/// <exception cref="XmpException"> </exception> /// <seealso cref= XMPMeta#getLocalizedText(String, String, String, String) </seealso> public virtual IXmpProperty GetLocalizedText(string schemaNs, string altTextName, string genericLang, string specificLang) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(altTextName); ParameterAsserts.AssertSpecificLang(specificLang); genericLang = genericLang != null?Utils.NormalizeLangValue(genericLang) : null; specificLang = Utils.NormalizeLangValue(specificLang); XmpPath arrayPath = XmpPathParser.ExpandXPath(schemaNs, altTextName); XmpNode arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, false, null); if (arrayNode == null) { return(null); } object[] result = XmpNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang); int match = (int)((int?)result[0]); XmpNode itemNode = (XmpNode)result[1]; if (match != XmpNodeUtils.CLT_NO_VALUES) { return(new XmpPropertyImpl1(itemNode)); } return(null); }
/// <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="XmpException"> Thrown if parsing or normalisation fails. </exception> public static XMPMeta Parse(object input, ParseOptions options) { ParameterAsserts.AssertNotNull(input); options = options ?? new ParseOptions(); XmlDocument document = ParseXml(input, options); bool xmpmetaRequired = options.RequireXmpMeta; object[] result = new object[3]; result = FindRootNode(document, xmpmetaRequired, result); if (result != null && result[1] == XmpRdf) { XmpMetaImpl xmp = ParseRdf.Parse((XmlNode)result[0]); xmp.PacketHeader = (string)result[2]; // Check if the XMP object shall be normalized if (!options.OmitNormalization) { return(XmpNormalizer.Process(xmp, options)); } return(xmp); } // no appropriate root node found, return empty metadata object return(new XmpMetaImpl()); }
/// <seealso cref= XMPMeta#getArrayItem(String, String, int) </seealso> public virtual IXmpProperty GetArrayItem(string schemaNs, string arrayName, int itemIndex) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); string itemPath = XmpPathFactory.ComposeArrayItemPath(arrayName, itemIndex); return(GetProperty(schemaNs, itemPath)); }
/// <seealso cref= XMPMeta#setStructField(String, String, String, String, String, /// PropertyOptions) </seealso> public virtual void SetStructField(string schemaNs, string structName, string fieldNs, string fieldName, string fieldValue, PropertyOptions options) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertStructName(structName); string fieldPath = structName + XmpPathFactory.ComposeStructFieldPath(fieldNs, fieldName); SetProperty(schemaNs, fieldPath, fieldValue, options); }
/// <seealso cref= XMPMeta#getStructField(String, String, String, String) </seealso> public virtual IXmpProperty GetStructField(string schemaNs, string structName, string fieldNs, string fieldName) { // fieldNs and fieldName are checked inside composeStructFieldPath ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertStructName(structName); string fieldPath = structName + XmpPathFactory.ComposeStructFieldPath(fieldNs, fieldName); return(GetProperty(schemaNs, fieldPath)); }
/// <exception cref="XmpException"> </exception> /// <seealso cref= XMPMeta#getQualifier(String, String, String, String) </seealso> public virtual IXmpProperty GetQualifier(string schemaNs, string propName, string qualNs, string qualName) { // qualNs and qualName are checked inside composeQualfierPath ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertPropName(propName); string qualPath = propName + XmpPathFactory.ComposeQualifierPath(qualNs, qualName); return(GetProperty(schemaNs, qualPath)); }
/// <seealso cref= XMPMeta#doesArrayItemExist(String, String, int) </seealso> public virtual bool DoesArrayItemExist(string schemaNs, string arrayName, int itemIndex) { try { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); string path = XmpPathFactory.ComposeArrayItemPath(arrayName, itemIndex); return(DoesPropertyExist(schemaNs, path)); } catch (XmpException) { return(false); } }
/// <seealso cref= XMPMeta#deleteArrayItem(String, String, int) </seealso> public virtual void DeleteArrayItem(string schemaNs, string arrayName, int itemIndex) { try { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); string itemPath = XmpPathFactory.ComposeArrayItemPath(arrayName, itemIndex); DeleteProperty(schemaNs, itemPath); } catch (XmpException) { // EMPTY, exceptions are ignored within delete } }
/// <seealso cref= XMPMeta#doesQualifierExist(String, String, String, String) </seealso> public virtual bool DoesQualifierExist(string schemaNs, string propName, string qualNs, string qualName) { try { // qualNs and qualName are checked inside composeQualifierPath() ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertPropName(propName); string path = XmpPathFactory.ComposeQualifierPath(qualNs, qualName); return(DoesPropertyExist(schemaNs, propName + path)); } catch (XmpException) { return(false); } }
/// <seealso cref= XMPMeta#doesStructFieldExist(String, String, String, String) </seealso> public virtual bool DoesStructFieldExist(string schemaNs, string structName, string fieldNs, string fieldName) { try { // fieldNs and fieldName are checked inside composeStructFieldPath() ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertStructName(structName); string path = XmpPathFactory.ComposeStructFieldPath(fieldNs, fieldName); return(DoesPropertyExist(schemaNs, structName + path)); } catch (XmpException) { return(false); } }
/// <seealso cref= XMPMeta#doesPropertyExist(String, String) </seealso> public virtual bool DoesPropertyExist(string schemaNs, string propName) { try { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertPropName(propName); XmpPath expPath = XmpPathParser.ExpandXPath(schemaNs, propName); XmpNode propNode = XmpNodeUtils.FindNode(_tree, expPath, false, null); return(propNode != null); } catch (XmpException) { return(false); } }
/// <seealso cref= XMPMeta#deleteStructField(String, String, String, String) </seealso> public virtual void DeleteStructField(string schemaNs, string structName, string fieldNs, string fieldName) { try { // fieldNs and fieldName are checked inside composeStructFieldPath ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertStructName(structName); string fieldPath = structName + XmpPathFactory.ComposeStructFieldPath(fieldNs, fieldName); DeleteProperty(schemaNs, fieldPath); } catch (XmpException) { // EMPTY, exceptions within delete are ignored } }
/// <seealso cref= XMPMeta#deleteQualifier(String, String, String, String) </seealso> public virtual void DeleteQualifier(string schemaNs, string propName, string qualNs, string qualName) { try { // Note: qualNs and qualName are checked inside composeQualfierPath ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertPropName(propName); string qualPath = propName + XmpPathFactory.ComposeQualifierPath(qualNs, qualName); DeleteProperty(schemaNs, qualPath); } catch (XmpException) { // EMPTY, exceptions within delete are ignored } }
private void RegisterAlias(string aliasNs, string aliasProp, string actualNs, string actualProp, AliasOptions aliasForm) { ParameterAsserts.AssertSchemaNs(aliasNs); ParameterAsserts.AssertPropName(aliasProp); ParameterAsserts.AssertSchemaNs(actualNs); ParameterAsserts.AssertPropName(actualProp); // Fix the alias options AliasOptions aliasOpts = aliasForm != null ? new AliasOptions( XmpNodeUtils.VerifySetOptions(aliasForm.ToPropertyOptions(), null). Options) : new AliasOptions(); if (_regex.IsMatch(aliasProp) || _regex.IsMatch(actualProp)) { throw new XmpException("Alias and actual property names must be simple", XmpError.BADXPATH); } // check if both namespaces are registered string aliasPrefix = GetNamespacePrefix(aliasNs); string actualPrefix = GetNamespacePrefix(actualNs); if (aliasPrefix == null) { throw new XmpException("Alias namespace is not registered", XmpError.BADSCHEMA); } if (actualPrefix == null) { throw new XmpException("Actual namespace is not registered", XmpError.BADSCHEMA); } string key = aliasPrefix + aliasProp; // check if alias is already existing if (_aliasMap.Contains(key)) { throw new XmpException("Alias is already existing", XmpError.BADPARAM); } if (_aliasMap.Contains(actualPrefix + actualProp)) { throw new XmpException("Actual property is already an alias, use the base property", XmpError.BADPARAM); } IXmpAliasInfo aliasInfo = new XmpAliasInfoImpl(actualNs, actualPrefix, actualProp, aliasOpts); _aliasMap[key] = aliasInfo; }
/// <seealso cref= XMPUtils#appendProperties(XMPMeta, XMPMeta, boolean, boolean) </seealso> /// <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="XmpException"> Forwards the Exceptions from the metadata processing </exception> public static void AppendProperties(IXmpMeta source, IXmpMeta destination, bool doAllProperties, bool replaceOldValues, bool deleteEmptyValues) { ParameterAsserts.AssertImplementation(source); ParameterAsserts.AssertImplementation(destination); XmpMetaImpl src = (XmpMetaImpl)source; XmpMetaImpl dest = (XmpMetaImpl)destination; for (IEnumerator it = src.Root.IterateChildren(); it.MoveNext();) { XmpNode sourceSchema = (XmpNode)it.Current; if (sourceSchema == null) { continue; } // Make sure we have a destination schema node XmpNode destSchema = XmpNodeUtils.FindSchemaNode(dest.Root, sourceSchema.Name, false); bool createdSchema = false; if (destSchema == null) { PropertyOptions propertyOptions = new PropertyOptions(); propertyOptions.SchemaNode = true; destSchema = new XmpNode(sourceSchema.Name, sourceSchema.Value, propertyOptions); dest.Root.AddChild(destSchema); createdSchema = true; } // Process the source schema's children. for (IEnumerator ic = sourceSchema.IterateChildren(); ic.MoveNext();) { XmpNode sourceProp = (XmpNode)ic.Current; if (sourceProp == null) { continue; } if (doAllProperties || !Utils.IsInternalProperty(sourceSchema.Name, sourceProp.Name)) { AppendSubtree(dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues); } } if (!destSchema.HasChildren() && (createdSchema || deleteEmptyValues)) { // Don't create an empty schema / remove empty schema. dest.Root.RemoveChild(destSchema); } } }
/// <exception cref="XmpException"> </exception> /// <seealso cref= XMPMeta#setQualifier(String, String, String, String, String, /// PropertyOptions) </seealso> public virtual void SetQualifier(string schemaNs, string propName, string qualNs, string qualName, string qualValue, PropertyOptions options) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertPropName(propName); if (!DoesPropertyExist(schemaNs, propName)) { throw new XmpException("Specified property does not exist!", XmpError.BADXPATH); } string qualPath = propName + XmpPathFactory.ComposeQualifierPath(qualNs, qualName); SetProperty(schemaNs, qualPath, qualValue, options); }
/// <seealso cref= XMPMeta#deleteProperty(String, String) </seealso> public virtual void DeleteProperty(string schemaNs, string propName) { try { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertPropName(propName); XmpPath expPath = XmpPathParser.ExpandXPath(schemaNs, propName); XmpNode propNode = XmpNodeUtils.FindNode(_tree, expPath, false, null); if (propNode != null) { XmpNodeUtils.DeleteNode(propNode); } } catch (XmpException) { // EMPTY, exceptions are ignored within delete } }
/// <exception cref="XmpException"> </exception> /// <seealso cref= XMPMeta#insertArrayItem(String, String, int, String, /// PropertyOptions) </seealso> public virtual void InsertArrayItem(string schemaNs, string arrayName, int itemIndex, string itemValue, PropertyOptions options) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); // Just lookup, don't try to create. XmpPath arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName); XmpNode arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, false, null); if (arrayNode != null) { DoSetArrayItem(arrayNode, itemIndex, itemValue, options, true); } else { throw new XmpException("Specified array does not exist", XmpError.BADXPATH); } }
/// <exception cref="XmpException"> </exception> /// <seealso cref= XMPMeta#countArrayItems(String, String) </seealso> public virtual int CountArrayItems(string schemaNs, string arrayName) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); XmpPath arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName); XmpNode arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, false, null); if (arrayNode == null) { return(0); } if (arrayNode.Options.Array) { return(arrayNode.ChildrenLength); } throw new XmpException("The named property is not an array", XmpError.BADXPATH); }
/// <summary> /// Returns a property, but the result value can be requested. /// </summary> /// <seealso cref= XMPMeta#GetProperty(String, String) </seealso> /// <param name="schemaNs"> /// a schema namespace </param> /// <param name="propName"> /// a property name or path </param> /// <param name="valueType"> /// the type of the value, see VALUE_... </param> /// <returns> Returns the node value as an object according to the /// <code>valueType</code>. </returns> /// <exception cref="XmpException"> /// Collects any exception that occurs. </exception> protected internal virtual object GetPropertyObject(string schemaNs, string propName, int valueType) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertPropName(propName); XmpPath expPath = XmpPathParser.ExpandXPath(schemaNs, propName); XmpNode propNode = XmpNodeUtils.FindNode(_tree, expPath, false, null); if (propNode != null) { if (valueType != VALUE_STRING && propNode.Options.CompositeProperty) { throw new XmpException("Property must be simple when a value type is requested", XmpError.BADXPATH); } return(evaluateNodeValue(valueType, propNode)); } return(null); }
/// <exception cref="XmpException"> </exception> /// <seealso cref= XMPMeta#SetProperty(String, String, Object, PropertyOptions) </seealso> public virtual void SetProperty(string schemaNs, string propName, object propValue, PropertyOptions options) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertPropName(propName); options = XmpNodeUtils.VerifySetOptions(options, propValue); XmpPath expPath = XmpPathParser.ExpandXPath(schemaNs, propName); XmpNode propNode = XmpNodeUtils.FindNode(_tree, expPath, true, options); if (propNode != null) { SetNode(propNode, propValue, options, false); } else { throw new XmpException("Specified property does not exist", XmpError.BADXPATH); } }
public string RegisterNamespace(string namespaceUri, string suggestedPrefix) { ParameterAsserts.AssertSchemaNs(namespaceUri); ParameterAsserts.AssertPrefix(suggestedPrefix); if (suggestedPrefix[suggestedPrefix.Length - 1] != ':') { suggestedPrefix += ':'; } if (!Utils.IsXmlNameNs(suggestedPrefix.Substring(0, suggestedPrefix.Length - 1))) { throw new XmpException("The prefix is a bad XML name", XmpError.BADXML); } string registeredPrefix = (string)_namespaceToPrefixMap[namespaceUri]; string registeredNs = (string)_prefixToNamespaceMap[suggestedPrefix]; if (registeredPrefix != null) { // Return the actual prefix return(registeredPrefix); } if (registeredNs != null) { // the namespace is new, but the prefix is already engaged, // we generate a new prefix out of the suggested string generatedPrefix = suggestedPrefix; for (int i = 1; _prefixToNamespaceMap.Contains(generatedPrefix); i++) { generatedPrefix = suggestedPrefix.Substring(0, suggestedPrefix.Length - 1) + "_" + i + "_:"; } suggestedPrefix = generatedPrefix; } _prefixToNamespaceMap[suggestedPrefix] = namespaceUri; _namespaceToPrefixMap[namespaceUri] = suggestedPrefix; // Return the suggested prefix return(suggestedPrefix); }
/// <seealso cref= XMPUtils#removeProperties(XMPMeta, String, String, boolean, boolean) /// </seealso> /// <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="XmpException"> If metadata processing fails </exception> public static void RemoveProperties(IXmpMeta xmp, string schemaNs, string propName, bool doAllProperties, bool includeAliases) { ParameterAsserts.AssertImplementation(xmp); XmpMetaImpl xmpImpl = (XmpMetaImpl)xmp; if (!string.IsNullOrEmpty(propName)) { // 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 (string.IsNullOrEmpty(schemaNs)) { throw new XmpException("Property name requires schema namespace", XmpError.BADPARAM); } XmpPath expPath = XmpPathParser.ExpandXPath(schemaNs, propName); XmpNode propNode = XmpNodeUtils.FindNode(xmpImpl.Root, expPath, false, null); if (propNode != null) { if (doAllProperties || !Utils.IsInternalProperty(expPath.GetSegment((int)XmpPath.STEP_SCHEMA).Name, expPath.GetSegment((int)XmpPath.STEP_ROOT_PROP).Name)) { XmpNode parent = propNode.Parent; parent.RemoveChild(propNode); if (parent.Options.SchemaNode && !parent.HasChildren()) { // remove empty schema node parent.Parent.RemoveChild(parent); } } } } else if (!string.IsNullOrEmpty(schemaNs)) { // 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.Root, schemaNs, false); if (schemaNode != null) { if (RemoveSchemaChildren(schemaNode, doAllProperties)) { xmpImpl.Root.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. IXmpAliasInfo[] aliases = XmpMetaFactory.SchemaRegistry.FindAliases(schemaNs); for (int i = 0; i < aliases.Length; i++) { IXmpAliasInfo info = aliases[i]; XmpPath path = XmpPathParser.ExpandXPath(info.Namespace, info.PropName); XmpNode actualProp = XmpNodeUtils.FindNode(xmpImpl.Root, path, false, null); if (actualProp != null) { XmpNode parent = actualProp.Parent; 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. ArrayList schemasToRemove = new ArrayList(); for (IEnumerator it = xmpImpl.Root.IterateChildren(); it.MoveNext();) { XmpNode schema = (XmpNode)it.Current; if (schema == null) { continue; } if (RemoveSchemaChildren(schema, doAllProperties)) { schemasToRemove.Add(schema); } } foreach (XmpNode xmpNode in schemasToRemove) { xmpImpl.Root.Children.Remove(xmpNode); } schemasToRemove.Clear(); } }
/// <seealso cref= XMPMeta#appendArrayItem(String, String, PropertyOptions, String, /// PropertyOptions) </seealso> public virtual void AppendArrayItem(string schemaNs, string arrayName, PropertyOptions arrayOptions, string itemValue, PropertyOptions itemOptions) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); if (arrayOptions == null) { arrayOptions = new PropertyOptions(); } if (!arrayOptions.OnlyArrayOptions) { throw new XmpException("Only array form flags allowed for arrayOptions", XmpError.BADOPTIONS); } // Check if array options are set correctly. arrayOptions = XmpNodeUtils.VerifySetOptions(arrayOptions, null); // Locate or create the array. If it already exists, make sure the array // form from the options // parameter is compatible with the current state. XmpPath arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName); // Just lookup, don't try to create. XmpNode arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, false, null); if (arrayNode != null) { // The array exists, make sure the form is compatible. Zero // arrayForm means take what exists. if (!arrayNode.Options.Array) { throw new XmpException("The named property is not an array", XmpError.BADXPATH); } // if (arrayOptions != null && !arrayOptions.equalArrayTypes(arrayNode.getOptions())) // { // throw new XmpException("Mismatch of existing and specified array form", BADOPTIONS); // } } else { // The array does not exist, try to create it. if (arrayOptions.Array) { arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, true, arrayOptions); if (arrayNode == null) { throw new XmpException("Failure creating array node", XmpError.BADXPATH); } } else { // array options missing throw new XmpException("Explicit arrayOptions required to create new array", XmpError.BADOPTIONS); } } DoSetArrayItem(arrayNode, ARRAY_LAST_ITEM, itemValue, itemOptions, true); }
/// <summary> /// see {@link XMPUtils#separateArrayItems(XMPMeta, String, String, String, /// PropertyOptions, boolean)} /// </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="XmpException"> /// Forwards the Exceptions from the metadata processing </exception> public static void SeparateArrayItems(IXmpMeta 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", XmpError.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. int charKind = UCK_NORMAL; char ch = (char)0; int itemEnd = 0; int endPos = catedStr.Length; while (itemEnd < endPos) { string itemValue; int itemStart; // 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 == UCK_NORMAL || charKind == UCK_QUOTE) { break; } } if (itemStart >= endPos) { break; } int nextKind; if (charKind != UCK_QUOTE) { // 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 == UCK_NORMAL || charKind == UCK_QUOTE || (charKind == UCK_COMMA && preserveCommas)) { continue; } if (charKind != UCK_SPACE) { break; } if ((itemEnd + 1) < endPos) { ch = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(ch); if (nextKind == UCK_NORMAL || nextKind == UCK_QUOTE || (nextKind == UCK_COMMA && preserveCommas)) { continue; } } // Anything left? break; // Have multiple spaces, or a space followed by a // separator. } itemValue = catedStr.Substring(itemStart, itemEnd - itemStart); } 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 = ""; for (itemEnd = itemStart; itemEnd < endPos; itemEnd++) { ch = catedStr[itemEnd]; charKind = ClassifyCharacter(ch); if (charKind != UCK_QUOTE || !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. char nextChar; if ((itemEnd + 1) < endPos) { nextChar = catedStr[itemEnd + 1]; nextKind = ClassifyCharacter(nextChar); } else { nextKind = UCK_SEMICOLON; nextChar = (char)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.ChildrenLength; oldChild++) { if (itemValue.Equals(arrayNode.GetChild(oldChild).Value)) { foundIndex = oldChild; break; } } if (foundIndex < 0) { XmpNode newItem = new XmpNode(ARRAY_ITEM_NAME, itemValue, null); arrayNode.AddChild(newItem); } } }
/// <seealso cref= XMPUtils#catenateArrayItems(XMPMeta, String, String, String, String, /// boolean) /// </seealso> /// <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="XmpException"> /// Forwards the Exceptions from the metadata processing </exception> public static string CatenateArrayItems(IXmpMeta xmp, string schemaNs, string arrayName, string separator, string quotes, bool allowCommas) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(arrayName); ParameterAsserts.AssertImplementation(xmp); if (string.IsNullOrEmpty(separator)) { separator = "; "; } if (string.IsNullOrEmpty(quotes)) { quotes = "\""; } XmpMetaImpl xmpImpl = (XmpMetaImpl)xmp; // Return an empty result if the array does not exist, // hurl if it isn't the right form. XmpPath arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName); XmpNode arrayNode = XmpNodeUtils.FindNode(xmpImpl.Root, arrayPath, false, null); if (arrayNode == null) { return(""); } if (!arrayNode.Options.Array || arrayNode.Options.ArrayAlternate) { throw new XmpException("Named property must be non-alternate array", XmpError.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 (IEnumerator it = arrayNode.IterateChildren(); it.MoveNext();) { XmpNode currItem = (XmpNode)it.Current; if (currItem == null) { continue; } if (currItem.Options.CompositeProperty) { throw new XmpException("Array items must be simple", XmpError.BADPARAM); } string str = ApplyQuotes(currItem.Value, openQuote, closeQuote, allowCommas); catinatedString.Append(str); if (it.MoveNext()) { catinatedString.Append(separator); } } return(catinatedString.ToString()); }
/// <seealso cref= XMPMeta#setLocalizedText(String, String, String, String, String, /// PropertyOptions) </seealso> public virtual void SetLocalizedText(string schemaNs, string altTextName, string genericLang, string specificLang, string itemValue, PropertyOptions options) { ParameterAsserts.AssertSchemaNs(schemaNs); ParameterAsserts.AssertArrayName(altTextName); ParameterAsserts.AssertSpecificLang(specificLang); genericLang = genericLang != null?Utils.NormalizeLangValue(genericLang) : null; specificLang = Utils.NormalizeLangValue(specificLang); XmpPath arrayPath = XmpPathParser.ExpandXPath(schemaNs, altTextName); // Find the array node and set the options if it was just created. XmpNode arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, true, new PropertyOptions(PropertyOptions.ARRAY | PropertyOptions.ARRAY_ORDERED | PropertyOptions.ARRAY_ALTERNATE | PropertyOptions.ARRAY_ALT_TEXT)); if (arrayNode == null) { throw new XmpException("Failed to find or create array node", XmpError.BADXPATH); } if (!arrayNode.Options.ArrayAltText) { if (!arrayNode.HasChildren() && arrayNode.Options.ArrayAlternate) { arrayNode.Options.ArrayAltText = true; } else { throw new XmpException("Specified property is no alt-text array", XmpError.BADXPATH); } } // Make sure the x-default item, if any, is first. bool haveXDefault = false; XmpNode xdItem = null; foreach (XmpNode currItem in arrayNode.Children) { if (!currItem.HasQualifier() || !XML_LANG.Equals(currItem.GetQualifier(1).Name)) { throw new XmpException("Language qualifier must be first", XmpError.BADXPATH); } if (X_DEFAULT.Equals(currItem.GetQualifier(1).Value)) { xdItem = currItem; haveXDefault = true; break; } } // Moves x-default to the beginning of the array if (xdItem != null && arrayNode.ChildrenLength > 1) { arrayNode.RemoveChild(xdItem); arrayNode.AddChild(1, xdItem); } // Find the appropriate item. // chooseLocalizedText will make sure the array is a language // alternative. object[] result = XmpNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang); int match = (int)((int?)result[0]); XmpNode itemNode = (XmpNode)result[1]; bool specificXDefault = X_DEFAULT.Equals(specificLang); switch (match) { case XmpNodeUtils.CLT_NO_VALUES: // Create the array items for the specificLang and x-default, with // x-default first. XmpNodeUtils.AppendLangItem(arrayNode, X_DEFAULT, itemValue); haveXDefault = true; if (!specificXDefault) { XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); } break; case XmpNodeUtils.CLT_SPECIFIC_MATCH: if (!specificXDefault) { // Update the specific item, update x-default if it matches the // old value. if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.Value.Equals(itemNode.Value)) { xdItem.Value = itemValue; } // ! Do this after the x-default check! itemNode.Value = itemValue; } else { // Update all items whose values match the old x-default value. Debug.Assert(haveXDefault && xdItem == itemNode); foreach (XmpNode currItem in arrayNode.Children) { if (currItem == xdItem || !currItem.Value.Equals(xdItem != null ? xdItem.Value : null)) { continue; } currItem.Value = itemValue; } // And finally do the x-default item. if (xdItem != null) { xdItem.Value = itemValue; } } break; case XmpNodeUtils.CLT_SINGLE_GENERIC: // Update the generic item, update x-default if it matches the old // value. if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.Value.Equals(itemNode.Value)) { xdItem.Value = itemValue; } itemNode.Value = itemValue; // ! Do this after // the x-default // check! break; case XmpNodeUtils.CLT_MULTIPLE_GENERIC: // Create the specific language, ignore x-default. XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); if (specificXDefault) { haveXDefault = true; } break; case XmpNodeUtils.CLT_XDEFAULT: // Create the specific language, update x-default if it was the only // item. if (xdItem != null && arrayNode.ChildrenLength == 1) { xdItem.Value = itemValue; } XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); break; case XmpNodeUtils.CLT_FIRST_ITEM: // Create the specific language, don't add an x-default item. XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue); if (specificXDefault) { haveXDefault = true; } break; default: // does not happen under normal circumstances throw new XmpException("Unexpected result from ChooseLocalizedText", XmpError.INTERNALFAILURE); } // Add an x-default at the front if needed. if (!haveXDefault && arrayNode.ChildrenLength == 1) { XmpNodeUtils.AppendLangItem(arrayNode, X_DEFAULT, itemValue); } }