/// <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> /// Tweak old XMP: Move an instance ID from rdf:about to the /// <em>xmpMM:InstanceID</em> property. An old instance ID usually looks /// like "uuid:bac965c4-9d87-11d9-9a30-000d936b79c4", plus InDesign /// 3.0 wrote them like "bac965c4-9d87-11d9-9a30-000d936b79c4". If /// the name looks like a UUID simply move it to <em>xmpMM:InstanceID</em>, /// don't worry about any existing <em>xmpMM:InstanceID</em>. Both will /// only be present when a newer file with the <em>xmpMM:InstanceID</em> /// property is updated by an old app that uses <em>rdf:about</em>. /// </summary> /// <param name="tree"> the root of the metadata tree </param> /// <exception cref="XmpException"> Thrown if tweaking fails. </exception> private static void TweakOldXmp(XmpNode tree) { if (tree.Name != null && tree.Name.Length >= Utils.UUID_LENGTH) { string nameStr = tree.Name.ToLower(); if (nameStr.StartsWith("uuid:")) { nameStr = nameStr.Substring(5); } if (Utils.CheckUuidFormat(nameStr)) { // move UUID to xmpMM:InstanceID and remove it from the root node XmpPath path = XmpPathParser.ExpandXPath(XmpConst.NS_XMP_MM, "InstanceID"); XmpNode idNode = XmpNodeUtils.FindNode(tree, path, true, null); if (idNode != null) { idNode.Options = null; // Clobber any existing xmpMM:InstanceID. idNode.Value = "uuid:" + nameStr; idNode.RemoveChildren(); idNode.RemoveQualifiers(); tree.Name = null; } else { throw new XmpException("Failure creating xmpMM:InstanceID", XmpError.INTERNALFAILURE); } } } }
/// <summary> /// Constructor with optionsl initial values. If <code>propName</code> is provided, /// <code>schemaNs</code> has also be provided. </summary> /// <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 <seealso cref="IteratorOptions"/> </param> /// <exception cref="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 _options = options ?? new IteratorOptions(); // the start node of the iteration depending on the schema and property filter XmpNode startNode; string initialPath = null; bool baseSchema = !String.IsNullOrEmpty(schemaNs); bool baseProperty = !String.IsNullOrEmpty(propPath); if (!baseSchema && !baseProperty) { // complete tree will be iterated startNode = xmp.Root; } 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.Root, path, false, null); _baseNs = schemaNs; initialPath = basePath.ToString(); } else if (baseSchema && !baseProperty) { // Only Schema provided startNode = XmpNodeUtils.FindSchemaNode(xmp.Root, schemaNs, false); } else // !baseSchema && baseProperty { // No schema but property provided -> error throw new XmpException("Schema namespace URI is required", XmpError.BADSCHEMA); } // create iterator if (startNode != null) { _nodeIterator = (!_options.JustChildren) ? new NodeIterator(this, startNode, initialPath, 1) : new NodeIteratorChildren(this, startNode, initialPath); } else { // create null iterator _nodeIterator = EmptyList.GetEnumerator(); } }
/// <summary>Constructor with optional initial values.</summary> /// <remarks>If <c>propName</c> is provided, <c>schemaNS</c> 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 reduced to this property within the <c>schemaNS</c></param> /// <param name="options">advanced iteration options, see <see cref="IteratorOptions"/></param> /// <exception cref="XmpException">If the node defined by the parameters is not existing.</exception> public XmpIterator(XmpMeta xmp, string schemaNs, string propPath, IteratorOptions options) { // make sure that options is defined at least with defaults Options = options ?? new IteratorOptions(); // the start node of the iteration depending on the schema and property filter XmpNode startNode = null; string initialPath = null; var baseSchema = !string.IsNullOrEmpty(schemaNs); var baseProperty = !string.IsNullOrEmpty(propPath); if (!baseSchema && !baseProperty) { // complete tree will be iterated startNode = xmp.GetRoot(); } else { if (baseSchema && baseProperty) { // Schema and property node provided var path = XmpPathParser.ExpandXPath(schemaNs, propPath); // base path is the prop path without the property leaf var basePath = new XmpPath(); for (var i = 0; i < path.Size() - 1; i++) { basePath.Add(path.GetSegment(i)); } startNode = XmpNodeUtils.FindNode(xmp.GetRoot(), path, false, null); BaseNamespace = 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", XmpErrorCode.BadSchema); } } } // create iterator _nodeIterator = startNode != null ? (IIterator)(!Options.IsJustChildren ? new NodeIterator(this, startNode, initialPath, 1) : new NodeIteratorChildren(this, startNode, initialPath)) : Enumerable.Empty <object>().Iterator(); }
/// <summary> /// Compose the path expression to select an alternate item by a field's value. The path syntax /// allows two forms of "content addressing" that may be used to select an item in an /// array of alternatives. The form used in ComposeFieldSelector lets you select an item in an /// array of structs based on the value of one of the fields in the structs. The other form of /// content addressing is shown in ComposeLangSelector. For example, consider a simple struct /// that has two fields, the name of a city and the URI of an FTP site in that city. Use this to /// create an array of download alternatives. You can show the user a popup built from the values /// of the city fields. You can then get the corresponding URI as follows: /// <p> /// <blockquote> /// /// <pre> /// String path = composeFieldSelector ( schemaNs, "Downloads", fieldNs, /// "City", chosenCity ); /// XMPProperty prop = xmpObj.getStructField ( schemaNs, path, fieldNs, "URI" ); /// </pre> /// /// </blockquote> /// </summary> /// <param name="arrayName"> The name of the array. May be a general path expression, must not be /// <code>null</code> or the empty string. </param> /// <param name="fieldNs"> The namespace URI for the field used as the selector. Must not be /// <code>null</code> or the empty string. </param> /// <param name="fieldName"> The name of the field used as the selector. Must be a simple XML name, must /// not be <code>null</code> or the empty string. It must be the name of a field that is /// itself simple. </param> /// <param name="fieldValue"> The desired value of the field. </param> /// <returns> Returns the composed path. This will be of the form /// <tt>ns:arrayName[fNS:fieldName='fieldValue']</tt>, where "ns" is the /// prefix for schemaNs and "fNS" is the prefix for fieldNs. </returns> /// <exception cref="XmpException"> Thrown if the path to create is not valid. </exception> public static string ComposeFieldSelector(string arrayName, string fieldNs, string fieldName, string fieldValue) { XmpPath fieldPath = XmpPathParser.ExpandXPath(fieldNs, fieldName); if (fieldPath.Size() != 2) { throw new XmpException("The fieldName name must be simple", XmpError.BADXPATH); } return(arrayName + '[' + fieldPath.GetSegment((int)XmpPath.STEP_ROOT_PROP).Name + "=\"" + fieldValue + "\"]"); }
/// <summary> /// Compose the path expression for a qualifier. /// </summary> /// <param name="qualNs"> The namespace URI for the qualifier. May be <code>null</code> or the empty /// string if the qualifier is in the XML empty namespace. </param> /// <param name="qualName"> The name of the qualifier. Must be a simple XML name, must not be /// <code>null</code> or the empty string. </param> /// <returns> Returns the composed path. This will be of the form /// <tt>ns:propName/?qNS:qualName</tt>, where "ns" is the prefix for /// schemaNs and "qNS" is the prefix for qualNs. </returns> /// <exception cref="XmpException"> Thrown if the path to create is not valid. </exception> public static string ComposeQualifierPath(string qualNs, string qualName) { AssertQualNs(qualNs); AssertQualName(qualName); XmpPath qualPath = XmpPathParser.ExpandXPath(qualNs, qualName); if (qualPath.Size() != 2) { throw new XmpException("The qualifier name must be simple", XmpError.BADXPATH); } return("/?" + qualPath.GetSegment((int)XmpPath.STEP_ROOT_PROP).Name); }
/// <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); } }
/// <summary> /// Compose the path expression for a field in a struct. The result can be added to the /// path of /// /// </summary> /// <param name="fieldNs"> The namespace URI for the field. Must not be <code>null</code> or the empty /// string. </param> /// <param name="fieldName"> The name of the field. Must be a simple XML name, must not be /// <code>null</code> or the empty string. </param> /// <returns> Returns the composed path. This will be of the form /// <tt>ns:structName/fNS:fieldName</tt>, where "ns" is the prefix for /// schemaNs and "fNS" is the prefix for fieldNs. </returns> /// <exception cref="XmpException"> Thrown if the path to create is not valid. </exception> public static string ComposeStructFieldPath(string fieldNs, string fieldName) { AssertFieldNs(fieldNs); AssertFieldName(fieldName); XmpPath fieldPath = XmpPathParser.ExpandXPath(fieldNs, fieldName); if (fieldPath.Size() != 2) { throw new XmpException("The field name must be simple", XmpError.BADXPATH); } return('/' + fieldPath.GetSegment((int)XmpPath.STEP_ROOT_PROP).Name); }
/// <summary> /// Parses the root node of an XMP Path, checks if namespace and prefix fit together /// and resolve the property to the base property if it is an alias. /// </summary> /// <param name="schemaNs">the root namespace</param> /// <param name="pos">the parsing position helper</param> /// <param name="expandedXPath">the path to contribute to</param> /// <exception cref="XmpException">If the path is not valid.</exception> private static void ParseRootNode(string schemaNs, PathPosition pos, XmpPath expandedXPath) { while (pos.StepEnd < pos.Path.Length && "/[*".IndexOf(pos.Path[pos.StepEnd]) < 0) { pos.StepEnd++; } if (pos.StepEnd == pos.StepBegin) { throw new XmpException("Empty initial XMPPath step", XmpErrorCode.BadXPath); } var rootProp = VerifyXPathRoot(schemaNs, pos.Path.Substring(pos.StepBegin, pos.StepEnd - pos.StepBegin)); var aliasInfo = XmpMetaFactory.SchemaRegistry.FindAlias(rootProp); if (aliasInfo == null) { // add schema xpath step expandedXPath.Add(new XmpPathSegment(schemaNs, XmpPathStepType.SchemaNode)); var rootStep = new XmpPathSegment(rootProp, XmpPathStepType.StructFieldStep); expandedXPath.Add(rootStep); return; } // add schema xpath step and base step of alias expandedXPath.Add(new XmpPathSegment(aliasInfo.Namespace, XmpPathStepType.SchemaNode)); expandedXPath.Add(new XmpPathSegment(VerifyXPathRoot(aliasInfo.Namespace, aliasInfo.PropName), XmpPathStepType.StructFieldStep) { IsAlias = true, AliasForm = aliasInfo.AliasForm.GetOptions() }); if (aliasInfo.AliasForm.IsArrayAltText) { expandedXPath.Add(new XmpPathSegment("[?xml:lang='x-default']", XmpPathStepType.QualSelectorStep) { IsAlias = true, AliasForm = aliasInfo.AliasForm.GetOptions() }); } else if (aliasInfo.AliasForm.IsArray) { expandedXPath.Add(new XmpPathSegment("[1]", XmpPathStepType.ArrayIndexStep) { IsAlias = true, AliasForm = aliasInfo.AliasForm.GetOptions() }); } }
/// <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> /// 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="XmpException"> Forwards exceptions </exception> private static XmpNode SeparateFindCreateArray(string schemaNs, string arrayName, PropertyOptions arrayOptions, XmpMetaImpl xmp) { arrayOptions = XmpNodeUtils.VerifySetOptions(arrayOptions, null); if (!arrayOptions.OnlyArrayOptions) { throw new XmpException("Options can only provide array form", XmpError.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.Root, arrayPath, false, null); if (arrayNode != null) { // The array exists, make sure the form is compatible. Zero // arrayForm means take what exists. PropertyOptions arrayForm = arrayNode.Options; if (!arrayForm.Array || arrayForm.ArrayAlternate) { throw new XmpException("Named property must be non-alternate array", XmpError.BADXPATH); } if (arrayOptions.EqualArrayTypes(arrayForm)) { throw new XmpException("Mismatch of specified and existing array form", XmpError.BADXPATH); // *** Right error? } } else { // The array does not exist, try to create it. // don't modify the options handed into the method arrayOptions.Array = true; arrayNode = XmpNodeUtils.FindNode(xmp.Root, arrayPath, true, arrayOptions); if (arrayNode == null) { throw new XmpException("Failed to create named array", XmpError.BADXPATH); } } return(arrayNode); }
/// <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); } }
/// <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); }
/// <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); } }
/// <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(); } }
/// <summary>Follow an expanded path expression to find or create a node.</summary> /// <param name="xmpTree">the node to begin the search.</param> /// <param name="xpath">the complete xpath</param> /// <param name="createNodes"> /// flag if nodes shall be created /// (when called by <c>setProperty()</c>) /// </param> /// <param name="leafOptions"> /// the options for the created leaf nodes (only when /// <c>createNodes == true</c>). /// </param> /// <returns>Returns the node if found or created or <c>null</c>.</returns> /// <exception cref="XmpException"> /// An exception is only thrown if an error occurred, /// not if a node was not found. /// </exception> internal static XmpNode FindNode(XmpNode xmpTree, XmpPath xpath, bool createNodes, PropertyOptions leafOptions) { // check if xpath is set. if (xpath == null || xpath.Size() == 0) { throw new XmpException("Empty XMPPath", XmpErrorCode.BadXPath); } // Root of implicitly created subtree to possible delete it later. // Valid only if leaf is new. XmpNode rootImplicitNode = null; // resolve schema step var currNode = FindSchemaNode(xmpTree, xpath.GetSegment(XmpPath.StepSchema).Name, createNodes); if (currNode == null) { return(null); } if (currNode.IsImplicit) { currNode.IsImplicit = false; // Clear the implicit node bit. rootImplicitNode = currNode; } // Save the top most implicit node. // Now follow the remaining steps of the original XMPPath. try { for (var i = 1; i < xpath.Size(); i++) { currNode = FollowXPathStep(currNode, xpath.GetSegment(i), createNodes); if (currNode == null) { if (createNodes) { // delete implicitly created nodes DeleteNode(rootImplicitNode); } return(null); } if (currNode.IsImplicit) { // clear the implicit node flag currNode.IsImplicit = false; // if node is an ALIAS (can be only in root step, auto-create array // when the path has been resolved from a not simple alias type if (i == 1 && xpath.GetSegment(i).IsAlias&& xpath.GetSegment(i).AliasForm != 0) { currNode.Options.SetOption(xpath.GetSegment(i).AliasForm, true); } else { // "CheckImplicitStruct" in C++ if (i < xpath.Size() - 1 && xpath.GetSegment(i).Kind == XmpPathStepType.StructFieldStep && !currNode.Options.IsCompositeProperty) { currNode.Options.IsStruct = true; } } if (rootImplicitNode == null) { rootImplicitNode = currNode; } } } } catch (XmpException) { // Save the top most implicit node. // if new notes have been created prior to the error, delete them if (rootImplicitNode != null) { DeleteNode(rootImplicitNode); } throw; } if (rootImplicitNode != null) { // set options only if a node has been successful created currNode.Options.MergeWith(leafOptions); currNode.Options = currNode.Options; } return(currNode); }
/// <summary> /// Split an XMPPath expression apart at the conceptual steps, adding the /// root namespace prefix to the first property component. /// </summary> /// <remarks> /// The schema URI is put in the first (0th) slot in the expanded XMPPath. /// Check if the top level component is an alias, but don't resolve it. /// <para /> /// In the most verbose case steps are separated by '/', and each step can be /// of these forms: /// <list> /// <item> /// <term>prefix:name</term> /// <description>A top level property or struct field.</description> /// </item> /// <item> /// <term>[index]</term> /// <description>An element of an array.</description> /// </item> /// <item> /// <term>[last()]</term> /// <description>The last element of an array.</description> /// </item> /// <item> /// <term>[fieldName="value"]</term> /// <description>An element in an array of structs, chosen by a field value.</description> /// </item> /// <item> /// <term>[@xml:lang="value"]</term> /// <description>An element in an alt-text array, chosen by the xml:lang qualifier.</description> /// </item> /// <item> /// <term>[?qualName="value"]</term> /// <description>An element in an array, chosen by a qualifier value.</description> /// </item> /// <item> /// <term>@xml:lang</term> /// <description>An xml:lang qualifier.</description> /// </item> /// <item> /// <term>?qualName</term> /// <description>A general qualifier.</description> /// </item> /// </list> /// <para /> /// The logic is complicated though by shorthand for arrays, the separating /// '/' and leading '*' are optional. These are all equivalent: array/*[2] /// array/[2] array*[2] array[2] All of these are broken into the 2 steps /// "array" and "[2]". /// <para /> /// The value portion in the array selector forms is a string quoted by ''' /// or '"'. The value may contain any character including a doubled quoting /// character. The value may be empty. /// <para /> /// The syntax isn't checked, but an XML name begins with a letter or '_', /// and contains letters, digits, '.', '-', '_', and a bunch of special /// non-ASCII Unicode characters. An XML qualified name is a pair of names /// separated by a colon. /// </remarks> /// <param name="schemaNs">schema namespace</param> /// <param name="path">property name</param> /// <returns>Returns the expandet XMPPath.</returns> /// <exception cref="XmpException">Thrown if the format is not correct somehow.</exception> public static XmpPath ExpandXPath(string schemaNs, string path) { if (schemaNs == null || path == null) { throw new XmpException("Parameter must not be null", XmpErrorCode.BadParam); } var expandedXPath = new XmpPath(); var pos = new PathPosition { Path = path }; // Pull out the first component and do some special processing on it: add the schema // namespace prefix and and see if it is an alias. The start must be a "qualName". ParseRootNode(schemaNs, pos, expandedXPath); // Now continue to process the rest of the XMPPath string. while (pos.StepEnd < path.Length) { pos.StepBegin = pos.StepEnd; SkipPathDelimiter(path, pos); pos.StepEnd = pos.StepBegin; var segment = path[pos.StepBegin] != '[' ? ParseStructSegment(pos) : ParseIndexSegment(pos); if (segment.Kind == XmpPath.StructFieldStep) { if (segment.Name[0] == '@') { segment.Name = "?" + segment.Name.Substring(1); if (!"?xml:lang".Equals(segment.Name)) { throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath); } } if (segment.Name[0] == '?') { pos.NameStart++; segment.Kind = XmpPath.QualifierStep; } VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart)); } else if (segment.Kind == XmpPath.FieldSelectorStep) { if (segment.Name[1] == '@') { segment.Name = "[?" + segment.Name.Substring(2); if (!segment.Name.StartsWith("[?xml:lang=")) { throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath); } } if (segment.Name[1] == '?') { pos.NameStart++; segment.Kind = XmpPath.QualSelectorStep; VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart)); } } expandedXPath.Add(segment); } return(expandedXPath); }
/// <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()); }
/// <summary> /// Parses the root node of an XMP Path, checks if namespace and prefix fit together /// and resolve the property to the base property if it is an alias. /// </summary> /// <param name="schemaNs">the root namespace</param> /// <param name="pos">the parsing position helper</param> /// <param name="expandedXPath">the path to contribute to</param> /// <exception cref="XmpException">If the path is not valid.</exception> private static void ParseRootNode(string schemaNs, PathPosition pos, XmpPath expandedXPath) { while (pos.StepEnd < pos.Path.Length && "/[*".IndexOf(pos.Path[pos.StepEnd]) < 0) pos.StepEnd++; if (pos.StepEnd == pos.StepBegin) throw new XmpException("Empty initial XMPPath step", XmpErrorCode.BadXPath); var rootProp = VerifyXPathRoot(schemaNs, pos.Path.Substring (pos.StepBegin, pos.StepEnd - pos.StepBegin)); var aliasInfo = XmpMetaFactory.SchemaRegistry.FindAlias(rootProp); if (aliasInfo == null) { // add schema xpath step expandedXPath.Add(new XmpPathSegment(schemaNs, XmpPath.SchemaNode)); var rootStep = new XmpPathSegment(rootProp, XmpPath.StructFieldStep); expandedXPath.Add(rootStep); } else { // add schema xpath step and base step of alias expandedXPath.Add(new XmpPathSegment(aliasInfo.Namespace, XmpPath.SchemaNode)); var rootStep = new XmpPathSegment(VerifyXPathRoot(aliasInfo.Namespace, aliasInfo.PropName), XmpPath.StructFieldStep); rootStep.IsAlias = true; rootStep.AliasForm = aliasInfo.AliasForm.GetOptions(); expandedXPath.Add(rootStep); if (aliasInfo.AliasForm.IsArrayAltText) { var qualSelectorStep = new XmpPathSegment("[?xml:lang='x-default']", XmpPath.QualSelectorStep); qualSelectorStep.IsAlias = true; qualSelectorStep.AliasForm = aliasInfo.AliasForm.GetOptions(); expandedXPath.Add(qualSelectorStep); } else if (aliasInfo.AliasForm.IsArray) { var indexStep = new XmpPathSegment("[1]", XmpPath.ArrayIndexStep); indexStep.IsAlias = true; indexStep.AliasForm = aliasInfo.AliasForm.GetOptions(); expandedXPath.Add(indexStep); } } }
/// <summary> /// Split an XMPPath expression apart at the conceptual steps, adding the /// root namespace prefix to the first property component. /// </summary> /// <remarks> /// The schema URI is put in the first (0th) slot in the expanded XMPPath. /// Check if the top level component is an alias, but don't resolve it. /// <para /> /// In the most verbose case steps are separated by '/', and each step can be /// of these forms: /// <list> /// <item> /// <term>prefix:name</term> /// <description>A top level property or struct field.</description> /// </item> /// <item> /// <term>[index]</term> /// <description>An element of an array.</description> /// </item> /// <item> /// <term>[last()]</term> /// <description>The last element of an array.</description> /// </item> /// <item> /// <term>[fieldName="value"]</term> /// <description>An element in an array of structs, chosen by a field value.</description> /// </item> /// <item> /// <term>[@xml:lang="value"]</term> /// <description>An element in an alt-text array, chosen by the xml:lang qualifier.</description> /// </item> /// <item> /// <term>[?qualName="value"]</term> /// <description>An element in an array, chosen by a qualifier value.</description> /// </item> /// <item> /// <term>@xml:lang</term> /// <description>An xml:lang qualifier.</description> /// </item> /// <item> /// <term>?qualName</term> /// <description>A general qualifier.</description> /// </item> /// </list> /// <para /> /// The logic is complicated though by shorthand for arrays, the separating /// '/' and leading '*' are optional. These are all equivalent: array/*[2] /// array/[2] array*[2] array[2] All of these are broken into the 2 steps /// "array" and "[2]". /// <para /> /// The value portion in the array selector forms is a string quoted by ''' /// or '"'. The value may contain any character including a doubled quoting /// character. The value may be empty. /// <para /> /// The syntax isn't checked, but an XML name begins with a letter or '_', /// and contains letters, digits, '.', '-', '_', and a bunch of special /// non-ASCII Unicode characters. An XML qualified name is a pair of names /// separated by a colon. /// </remarks> /// <param name="schemaNs">schema namespace</param> /// <param name="path">property name</param> /// <returns>Returns the expandet XMPPath.</returns> /// <exception cref="XmpException">Thrown if the format is not correct somehow.</exception> public static XmpPath ExpandXPath(string schemaNs, string path) { if (schemaNs == null || path == null) throw new XmpException("Parameter must not be null", XmpErrorCode.BadParam); var expandedXPath = new XmpPath(); var pos = new PathPosition { Path = path }; // Pull out the first component and do some special processing on it: add the schema // namespace prefix and and see if it is an alias. The start must be a "qualName". ParseRootNode(schemaNs, pos, expandedXPath); // Now continue to process the rest of the XMPPath string. while (pos.StepEnd < path.Length) { pos.StepBegin = pos.StepEnd; SkipPathDelimiter(path, pos); pos.StepEnd = pos.StepBegin; var segment = path[pos.StepBegin] != '[' ? ParseStructSegment(pos) : ParseIndexSegment(pos); if (segment.Kind == XmpPath.StructFieldStep) { if (segment.Name[0] == '@') { segment.Name = "?" + segment.Name.Substring (1); if (!"?xml:lang".Equals(segment.Name)) throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath); } if (segment.Name[0] == '?') { pos.NameStart++; segment.Kind = XmpPath.QualifierStep; } VerifyQualName(pos.Path.Substring (pos.NameStart, pos.NameEnd - pos.NameStart)); } else if (segment.Kind == XmpPath.FieldSelectorStep) { if (segment.Name[1] == '@') { segment.Name = "[?" + segment.Name.Substring (2); if (!segment.Name.StartsWith("[?xml:lang=")) throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath); } if (segment.Name[1] == '?') { pos.NameStart++; segment.Kind = XmpPath.QualSelectorStep; VerifyQualName(pos.Path.Substring (pos.NameStart, pos.NameEnd - pos.NameStart)); } } expandedXPath.Add(segment); } return expandedXPath; }