// EMPTY /// <summary> /// Parses the input source into an XMP metadata object, including /// de-aliasing and normalisation. /// </summary> /// <param name="input"> /// the input can be an <code>InputStream</code>, a <code>String</code> or /// a byte buffer containing the XMP packet. /// </param> /// <param name="options">the parse options</param> /// <returns>Returns the resulting XMP metadata object</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if parsing or normalisation fails.</exception> public static XMPMeta Parse(object input, ParseOptions options) { ParameterAsserts.AssertNotNull(input); options = options != null ? options : new ParseOptions(); XmlDocument document = ParseXml(input, options); bool xmpmetaRequired = options.GetRequireXMPMeta(); object[] result = new object[3]; result = FindRootNode(document, xmpmetaRequired, result); if (result != null && result[1] == XmpRdf) { XMPMetaImpl xmp = ParseRDF.Parse((XmlNode)result[0]); xmp.SetPacketHeader((string)result[2]); // Check if the XMP object shall be normalized if (!options.GetOmitNormalization()) { return(XMPNormalizer.Process(xmp, options)); } else { return(xmp); } } else { // no appropriate root node found, return empty metadata object return(new XMPMetaImpl()); } }
/// <seealso cref="Com.Adobe.Xmp.XMPUtils.AppendProperties(Com.Adobe.Xmp.XMPMeta, Com.Adobe.Xmp.XMPMeta, bool, bool)"/> /// <param name="source">The source XMP object.</param> /// <param name="destination">The destination XMP object.</param> /// <param name="doAllProperties">Do internal properties in addition to external properties.</param> /// <param name="replaceOldValues">Replace the values of existing properties.</param> /// <param name="deleteEmptyValues">Delete destination values if source property is empty.</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards the Exceptions from the metadata processing</exception> public static void AppendProperties(XMPMeta source, XMPMeta destination, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) { ParameterAsserts.AssertImplementation(source); ParameterAsserts.AssertImplementation(destination); XMPMetaImpl src = (XMPMetaImpl)source; XMPMetaImpl dest = (XMPMetaImpl)destination; for (Iterator it = src.GetRoot().IterateChildren(); it.HasNext();) { XMPNode sourceSchema = (XMPNode)it.Next(); // Make sure we have a destination schema node XMPNode destSchema = XMPNodeUtils.FindSchemaNode(dest.GetRoot(), sourceSchema.GetName(), false); bool createdSchema = false; if (destSchema == null) { destSchema = new XMPNode(sourceSchema.GetName(), sourceSchema.GetValue(), new PropertyOptions().SetSchemaNode(true)); dest.GetRoot().AddChild(destSchema); createdSchema = true; } // Process the source schema's children. for (Iterator ic = sourceSchema.IterateChildren(); ic.HasNext();) { XMPNode sourceProp = (XMPNode)ic.Next(); if (doAllProperties || !Utils.IsInternalProperty(sourceSchema.GetName(), sourceProp.GetName())) { AppendSubtree(dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues); } } if (!destSchema.HasChildren() && (createdSchema || deleteEmptyValues)) { // Don't create an empty schema / remove empty schema. dest.GetRoot().RemoveChild(destSchema); } } }
/// <summary>Serializes an <code>XMPMeta</code>-object as RDF into a byte buffer.</summary> /// <param name="xmp">a metadata implementation object</param> /// <param name="options"> /// Options to control the serialization (see /// <see cref="Com.Adobe.Xmp.Options.SerializeOptions"/> /// ). /// </param> /// <returns>Returns a byte buffer containing the serialized RDF.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">on serializsation errors.</exception> public static sbyte[] SerializeToBuffer(XMPMetaImpl xmp, SerializeOptions options) { ByteArrayOutputStream @out = new ByteArrayOutputStream(2048); Serialize(xmp, @out, options); return(@out.ToByteArray()); }
// 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()); }
// EMPTY /// <summary>Normalizes a raw parsed XMPMeta-Object</summary> /// <param name="xmp">the raw metadata object</param> /// <param name="options">the parsing options</param> /// <returns>Returns the normalized metadata object</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">Collects all severe processing errors.</exception> internal static XMPMeta Process(XMPMetaImpl xmp, ParseOptions options) { XMPNode tree = xmp.GetRoot(); TouchUpDataModel(xmp); MoveExplicitAliases(tree, options); TweakOldXMP(tree); DeleteEmptySchemas(tree); return(xmp); }
/// <summary>Static method to serialize the metadata object.</summary> /// <remarks> /// Static method to serialize the metadata object. For each serialisation, a new XMPSerializer /// instance is created, either XMPSerializerRDF or XMPSerializerPlain so thats its possible to /// serialialize the same XMPMeta objects in two threads. /// </remarks> /// <param name="xmp">a metadata implementation object</param> /// <param name="out">the output stream to serialize to</param> /// <param name="options">serialization options, can be <code>null</code> for default.</param> /// <exception cref="Com.Adobe.Xmp.XMPException"/> public static void Serialize(XMPMetaImpl xmp, OutputStream @out, SerializeOptions options) { options = options != null ? options : new SerializeOptions(); // sort the internal data model on demand if (options.GetSort()) { xmp.Sort(); } new XMPSerializerRDF().Serialize(xmp, @out, options); }
/// <summary>Visit all schemas to do general fixes and handle special cases.</summary> /// <param name="xmp">the metadata object implementation</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Thrown if the normalisation fails.</exception> private static void TouchUpDataModel(XMPMetaImpl xmp) { // make sure the DC schema is existing, because it might be needed within the normalization // if not touched it will be removed by removeEmptySchemas XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), XMPConstConstants.NsDc, true); // Do the special case fixes within each schema. for (Iterator it = xmp.GetRoot().IterateChildren(); it.HasNext();) { XMPNode currSchema = (XMPNode)it.Next(); if (XMPConstConstants.NsDc.Equals(currSchema.GetName())) { NormalizeDCArrays(currSchema); } else { if (XMPConstConstants.NsExif.Equals(currSchema.GetName())) { // Do a special case fix for exif:GPSTimeStamp. FixGPSTimeStamp(currSchema); XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "exif:UserComment", false); if (arrayNode != null) { RepairAltText(arrayNode); } } else { if (XMPConstConstants.NsDm.Equals(currSchema.GetName())) { // Do a special case migration of xmpDM:copyright to // dc:rights['x-default']. XMPNode dmCopyright = XMPNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false); if (dmCopyright != null) { MigrateAudioCopyright(xmp, dmCopyright); } } else { if (XMPConstConstants.NsXmpRights.Equals(currSchema.GetName())) { XMPNode arrayNode = XMPNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false); if (arrayNode != null) { RepairAltText(arrayNode); } } } } } } }
/// <summary>Serializes an <code>XMPMeta</code>-object as RDF into a string.</summary> /// <remarks> /// Serializes an <code>XMPMeta</code>-object as RDF into a string. /// <em>Note:</em> Encoding is forced to UTF-16 when serializing to a /// string to ensure the correctness of "exact packet size". /// </remarks> /// <param name="xmp">a metadata implementation object</param> /// <param name="options"> /// Options to control the serialization (see /// <see cref="Com.Adobe.Xmp.Options.SerializeOptions"/> /// ). /// </param> /// <returns>Returns a string containing the serialized RDF.</returns> /// <exception cref="Com.Adobe.Xmp.XMPException">on serializsation errors.</exception> public static string SerializeToString(XMPMetaImpl xmp, SerializeOptions options) { // forces the encoding to be UTF-16 to get the correct string length options = options != null ? options : new SerializeOptions(); options.SetEncodeUTF16BE(true); ByteArrayOutputStream @out = new ByteArrayOutputStream(2048); Serialize(xmp, @out, options); try { return(@out.ToString(options.GetEncoding())); } catch (UnsupportedEncodingException) { // cannot happen as UTF-8/16LE/BE is required to be implemented in // Java return(@out.ToString()); } }
/// <seealso cref="AppendProperties(Com.Adobe.Xmp.XMPMeta, Com.Adobe.Xmp.XMPMeta, bool, bool, bool)"/> /// <param name="destXMP">The destination XMP object.</param> /// <param name="sourceNode">the source node</param> /// <param name="destParent">the parent of the destination node</param> /// <param name="replaceOldValues">Replace the values of existing properties.</param> /// <param name="deleteEmptyValues"> /// flag if properties with empty values should be deleted /// in the destination object. /// </param> /// <exception cref="Com.Adobe.Xmp.XMPException"/> private static void AppendSubtree(XMPMetaImpl destXMP, XMPNode sourceNode, XMPNode destParent, bool replaceOldValues, bool deleteEmptyValues) { XMPNode destNode = XMPNodeUtils.FindChildNode(destParent, sourceNode.GetName(), false); bool valueIsEmpty = false; if (deleteEmptyValues) { valueIsEmpty = sourceNode.GetOptions().IsSimple() ? sourceNode.GetValue() == null || sourceNode.GetValue().Length == 0 : !sourceNode.HasChildren(); } if (deleteEmptyValues && valueIsEmpty) { if (destNode != null) { destParent.RemoveChild(destNode); } } else { if (destNode == null) { // The one easy case, the destination does not exist. destParent.AddChild((XMPNode)sourceNode.Clone()); } else { if (replaceOldValues) { // The destination exists and should be replaced. destXMP.SetNode(destNode, sourceNode.GetValue(), sourceNode.GetOptions(), true); destParent.RemoveChild(destNode); destNode = (XMPNode)sourceNode.Clone(); destParent.AddChild(destNode); } else { // The destination exists and is not totally replaced. Structs and // arrays are merged. PropertyOptions sourceForm = sourceNode.GetOptions(); PropertyOptions destForm = destNode.GetOptions(); if (sourceForm != destForm) { return; } if (sourceForm.IsStruct()) { // To merge a struct process the fields recursively. E.g. add simple missing fields. // The recursive call to AppendSubtree will handle deletion for fields with empty // values. for (Iterator it = sourceNode.IterateChildren(); it.HasNext();) { XMPNode sourceField = (XMPNode)it.Next(); AppendSubtree(destXMP, sourceField, destNode, replaceOldValues, deleteEmptyValues); if (deleteEmptyValues && !destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else { if (sourceForm.IsArrayAltText()) { // Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first. // Make a special check for deletion of empty values. Meaningful in AltText arrays // because the "xml:lang" qualifier provides unambiguous source/dest correspondence. for (Iterator it = sourceNode.IterateChildren(); it.HasNext();) { XMPNode sourceItem = (XMPNode)it.Next(); if (!sourceItem.HasQualifier() || !XMPConstConstants.XmlLang.Equals(sourceItem.GetQualifier(1).GetName())) { continue; } int destIndex = XMPNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).GetValue()); if (deleteEmptyValues && (sourceItem.GetValue() == null || sourceItem.GetValue().Length == 0)) { if (destIndex != -1) { destNode.RemoveChild(destIndex); if (!destNode.HasChildren()) { destParent.RemoveChild(destNode); } } } else { if (destIndex == -1) { // Not replacing, keep the existing item. if (!XMPConstConstants.XDefault.Equals(sourceItem.GetQualifier(1).GetValue()) || !destNode.HasChildren()) { sourceItem.CloneSubtree(destNode); } else { XMPNode destItem = new XMPNode(sourceItem.GetName(), sourceItem.GetValue(), sourceItem.GetOptions()); sourceItem.CloneSubtree(destItem); destNode.AddChild(1, destItem); } } } } } else { if (sourceForm.IsArray()) { // Merge other arrays by item values. Don't worry about order or duplicates. Source // items with empty values do not cause deletion, that conflicts horribly with // merging. for (Iterator @is = sourceNode.IterateChildren(); @is.HasNext();) { XMPNode sourceItem = (XMPNode)@is.Next(); bool match = false; for (Iterator id = destNode.IterateChildren(); id.HasNext();) { XMPNode destItem = (XMPNode)id.Next(); if (ItemValuesMatch(sourceItem, destItem)) { match = true; } } if (!match) { destNode = (XMPNode)sourceItem.Clone(); destParent.AddChild(destNode); } } } } } } } } }
/// <seealso cref="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>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> /// see /// <see cref="Com.Adobe.Xmp.XMPUtils.SeparateArrayItems(Com.Adobe.Xmp.XMPMeta, string, string, string, Com.Adobe.Xmp.Options.PropertyOptions, bool)"/> /// </summary> /// <param name="xmp">The XMP object containing the array to be updated.</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="catedStr">The string to be separated into the array items.</param> /// <param name="arrayOptions">Option flags to control the separation.</param> /// <param name="preserveCommas">Flag if commas shall be preserved</param> /// <exception cref="Com.Adobe.Xmp.XMPException">Forwards the Exceptions from the metadata processing</exception> public static void SeparateArrayItems(XMPMeta xmp, string schemaNS, string arrayName, string catedStr, PropertyOptions arrayOptions, bool preserveCommas) { ParameterAsserts.AssertSchemaNS(schemaNS); ParameterAsserts.AssertArrayName(arrayName); if (catedStr == null) { throw new XMPException("Parameter must not be null", XMPErrorConstants.Badparam); } ParameterAsserts.AssertImplementation(xmp); XMPMetaImpl xmpImpl = (XMPMetaImpl)xmp; // Keep a zero value, has special meaning below. XMPNode arrayNode = SeparateFindCreateArray(schemaNS, arrayName, arrayOptions, xmpImpl); // Extract the item values one at a time, until the whole input string is done. string itemValue; int itemStart; int itemEnd; int nextKind = UckNormal; int charKind = UckNormal; char ch = (char)0; char nextChar = (char)0; itemEnd = 0; int endPos = catedStr.Length; while (itemEnd < endPos) { // Skip any leading spaces and separation characters. Always skip commas here. // They can be kept when within a value, but not when alone between values. for (itemStart = itemEnd; itemStart < endPos; itemStart++) { ch = catedStr[itemStart]; charKind = ClassifyCharacter(ch); if (charKind == UckNormal || charKind == UckQuote) { break; } } if (itemStart >= endPos) { break; } if (charKind != UckQuote) { // This is not a quoted value. Scan for the end, create an array // item from the substring. for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind == UckNormal || charKind == UckQuote || (charKind == UckComma && preserveCommas)) { continue; } else { if (charKind != UckSpace) { break; } else { if ((itemEnd + 1) < endPos) { ch = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(ch); if (nextKind == UckNormal || nextKind == UckQuote || (nextKind == UckComma && preserveCommas)) { continue; } } } } // Anything left? break; } // Have multiple spaces, or a space followed by a // separator. itemValue = Sharpen.Runtime.Substring(catedStr, itemStart, itemEnd); } else { // Accumulate quoted values into a local string, undoubling // internal quotes that // match the surrounding quotes. Do not undouble "unmatching" // quotes. char openQuote = ch; char closeQuote = GetClosingQuote(openQuote); itemStart++; // Skip the opening quote; itemValue = string.Empty; for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind != UckQuote || !IsSurroundingQuote(ch, openQuote, closeQuote)) { // This is not a matching quote, just append it to the // item value. itemValue += ch; } else { // This is a "matching" quote. Is it doubled, or the // final closing quote? // Tolerate various edge cases like undoubled opening // (non-closing) quotes, // or end of input. if ((itemEnd + 1) < endPos) { nextChar = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(nextChar); } else { nextKind = UckSemicolon; nextChar = (char)unchecked ((int)(0x3B)); } if (ch == nextChar) { // This is doubled, copy it and skip the double. itemValue += ch; // Loop will add in charSize. itemEnd++; } else { if (!IsClosingingQuote(ch, openQuote, closeQuote)) { // This is an undoubled, non-closing quote, copy it. itemValue += ch; } else { // This is an undoubled closing quote, skip it and // exit the loop. itemEnd++; break; } } } } } // Add the separated item to the array. // Keep a matching old value in case it had separators. int foundIndex = -1; for (int oldChild = 1; oldChild <= arrayNode.GetChildrenLength(); oldChild++) { if (itemValue.Equals(arrayNode.GetChild(oldChild).GetValue())) { foundIndex = oldChild; break; } } XMPNode newItem = null; if (foundIndex < 0) { newItem = new XMPNode(XMPConstConstants.ArrayItemName, itemValue, null); arrayNode.AddChild(newItem); } } }
/// <summary>Constructor with optionsl initial values.</summary> /// <remarks> /// Constructor with optionsl initial values. If <code>propName</code> is provided, /// <code>schemaNS</code> has also be provided. /// </remarks> /// <param name="xmp">the iterated metadata object.</param> /// <param name="schemaNS">the iteration is reduced to this schema (optional)</param> /// <param name="propPath">the iteration is redurce to this property within the <code>schemaNS</code></param> /// <param name="options"> /// advanced iteration options, see /// <see cref="Com.Adobe.Xmp.Options.IteratorOptions"/> /// </param> /// <exception cref="Com.Adobe.Xmp.XMPException">If the node defined by the paramters is not existing.</exception> public XMPIteratorImpl(XMPMetaImpl xmp, string schemaNS, string propPath, IteratorOptions options) { // make sure that options is defined at least with defaults this.options = options != null ? options : new IteratorOptions(); // the start node of the iteration depending on the schema and property filter XMPNode startNode = null; string initialPath = null; bool baseSchema = schemaNS != null && schemaNS.Length > 0; bool baseProperty = propPath != null && propPath.Length > 0; if (!baseSchema && !baseProperty) { // complete tree will be iterated startNode = xmp.GetRoot(); } else { if (baseSchema && baseProperty) { // Schema and property node provided XMPPath path = XMPPathParser.ExpandXPath(schemaNS, propPath); // base path is the prop path without the property leaf XMPPath basePath = new XMPPath(); for (int i = 0; i < path.Size() - 1; i++) { basePath.Add(path.GetSegment(i)); } startNode = XMPNodeUtils.FindNode(xmp.GetRoot(), path, false, null); baseNS = schemaNS; initialPath = basePath.ToString(); } else { if (baseSchema && !baseProperty) { // Only Schema provided startNode = XMPNodeUtils.FindSchemaNode(xmp.GetRoot(), schemaNS, false); } else { // !baseSchema && baseProperty // No schema but property provided -> error throw new XMPException("Schema namespace URI is required", XMPErrorConstants.Badschema); } } } // create iterator if (startNode != null) { if (!this.options.IsJustChildren()) { nodeIterator = new XMPIteratorImpl.NodeIterator(this, startNode, initialPath, 1); } else { nodeIterator = new XMPIteratorImpl.NodeIteratorChildren(this, startNode, initialPath); } } else { // create null iterator nodeIterator = Collections.EmptyList().ListIterator(); } }