/// <summary>
 /// Tweak old XMP: Move an instance ID from rdf:about to the
 /// <em>xmpMM:InstanceID</em> property.
 /// </summary>
 /// <remarks>
 /// Tweak old XMP: Move an instance ID from rdf:about to the
 /// <em>xmpMM:InstanceID</em> property. An old instance ID usually looks
 /// like &quot;uuid:bac965c4-9d87-11d9-9a30-000d936b79c4&quot;, plus InDesign
 /// 3.0 wrote them like &quot;bac965c4-9d87-11d9-9a30-000d936b79c4&quot;. 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>.
 /// </remarks>
 /// <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.UuidLength)
     {
         var 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
             var path   = XmpPathParser.ExpandXPath(XmpConstants.NsXmpMm, "InstanceID");
             var 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", XmpErrorCode.InternalFailure);
             }
         }
     }
 }
        /// <exception cref="XmpException"/>
        public 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);
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, altTextName);
            var arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, false, null);

            if (arrayNode == null)
            {
                return(null);
            }
            var result   = XmpNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang);
            var match    = (int)result[0];
            var itemNode = (XmpNode)result[1];

            if (match != XmpNodeUtils.CltNoValues)
            {
                return(new XmpProperty407(itemNode));
            }
            return(null);
        }
Exemple #3
0
        /// <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();
        }
        /// <exception cref="XmpException"/>
        public 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.IsOnlyArrayOptions)
            {
                throw new XmpException("Only array form flags allowed for arrayOptions", XmpErrorCode.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.
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName);
            // Just lookup, don't try to create.
            var 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.IsArray)
                {
                    throw new XmpException("The named property is not an array", XmpErrorCode.BadXPath);
                }
            }
            else
            {
                // if (arrayOptions != null && !arrayOptions.equalArrayTypes(arrayNode.getOptions()))
                // {
                // throw new XMPException("Mismatch of existing and specified array form", BADOPTIONS);
                // }
                // The array does not exist, try to create it.
                if (arrayOptions.IsArray)
                {
                    arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, true, arrayOptions);
                    if (arrayNode == null)
                    {
                        throw new XmpException("Failure creating array node", XmpErrorCode.BadXPath);
                    }
                }
                else
                {
                    // array options missing
                    throw new XmpException("Explicit arrayOptions required to create new array", XmpErrorCode.BadOptions);
                }
            }
            DoSetArrayItem(arrayNode, XmpConstants.ArrayLastItem, itemValue, itemOptions, true);
        }
 public bool DoesPropertyExist(string schemaNs, string propName)
 {
     try
     {
         ParameterAsserts.AssertSchemaNs(schemaNs);
         ParameterAsserts.AssertPropName(propName);
         var expPath  = XmpPathParser.ExpandXPath(schemaNs, propName);
         var propNode = XmpNodeUtils.FindNode(_tree, expPath, false, null);
         return(propNode != null);
     }
     catch (XmpException)
     {
         return(false);
     }
 }
        /// <exception cref="XmpException"/>
        public void SetProperty(string schemaNs, string propName, object propValue, PropertyOptions options)
        {
            ParameterAsserts.AssertSchemaNs(schemaNs);
            ParameterAsserts.AssertPropName(propName);
            options = XmpNodeUtils.VerifySetOptions(options, propValue);
            var expPath  = XmpPathParser.ExpandXPath(schemaNs, propName);
            var propNode = XmpNodeUtils.FindNode(_tree, expPath, true, options);

            if (propNode != null)
            {
                SetNode(propNode, propValue, options, false);
            }
            else
            {
                throw new XmpException("Specified property does not exist", XmpErrorCode.BadXPath);
            }
        }
        /// <exception cref="XmpException"/>
        public 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.
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName);
            var 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", XmpErrorCode.BadXPath);
            }
        }
        /// <summary>Returns a property, but the result value can be requested.</summary>
        /// <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
        /// <c>valueType</c>.
        /// </returns>
        /// <exception cref="XmpException">Collects any exception that occurs.</exception>
        private object GetPropertyObject(string schemaNs, string propName, ValueType valueType)
        {
            ParameterAsserts.AssertSchemaNs(schemaNs);
            ParameterAsserts.AssertPropName(propName);
            var expPath  = XmpPathParser.ExpandXPath(schemaNs, propName);
            var propNode = XmpNodeUtils.FindNode(_tree, expPath, false, null);

            if (propNode != null)
            {
                if (valueType != ValueType.String && propNode.Options.IsCompositeProperty)
                {
                    throw new XmpException("Property must be simple when a value type is requested", XmpErrorCode.BadXPath);
                }
                return(EvaluateNodeValue(valueType, propNode));
            }
            return(null);
        }
 public void DeleteProperty(string schemaNs, string propName)
 {
     try
     {
         ParameterAsserts.AssertSchemaNs(schemaNs);
         ParameterAsserts.AssertPropName(propName);
         var expPath  = XmpPathParser.ExpandXPath(schemaNs, propName);
         var propNode = XmpNodeUtils.FindNode(_tree, expPath, false, null);
         if (propNode != null)
         {
             XmpNodeUtils.DeleteNode(propNode);
         }
     }
     catch (XmpException)
     {
     }
 }
Exemple #10
0
        /// <exception cref="XmpException"/>
        public int CountArrayItems(string schemaNs, string arrayName)
        {
            ParameterAsserts.AssertSchemaNs(schemaNs);
            ParameterAsserts.AssertArrayName(arrayName);
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName);
            var arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, false, null);

            if (arrayNode == null)
            {
                return(0);
            }
            if (arrayNode.Options.IsArray)
            {
                return(arrayNode.GetChildrenLength());
            }
            throw new XmpException("The named property is not an array", XmpErrorCode.BadXPath);
        }
        /// <summary>Utility to find or create the array used by <c>separateArrayItems()</c>.</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, XmpMeta xmp)
        {
            arrayOptions = XmpNodeUtils.VerifySetOptions(arrayOptions, null);

            if (!arrayOptions.IsOnlyArrayOptions)
            {
                throw new XmpException("Options can only provide array form", XmpErrorCode.BadOptions);
            }

            // Find the array node, make sure it is OK. Move the current children
            // aside, to be readded later if kept.
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName);
            var 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.
                var arrayForm = arrayNode.Options;

                if (!arrayForm.IsArray || arrayForm.IsArrayAlternate)
                {
                    throw new XmpException("Named property must be non-alternate array", XmpErrorCode.BadXPath);
                }
                if (arrayOptions.EqualArrayTypes(arrayForm))
                {
                    throw new XmpException("Mismatch of specified and existing array form", XmpErrorCode.BadXPath);
                }
            }
            else
            {
                // *** Right error?
                // The array does not exist, try to create it.
                // don't modify the options handed into the method
                arrayOptions.IsArray = true;
                arrayNode            = XmpNodeUtils.FindNode(xmp.GetRoot(), arrayPath, true, arrayOptions);
                if (arrayNode == null)
                {
                    throw new XmpException("Failed to create named array", XmpErrorCode.BadXPath);
                }
            }
            return(arrayNode);
        }
        /// <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 &quot;; &quot;, 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 &apos;&quot;&apos;
        /// </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 = "\"";
            }

            var xmpImpl = (XmpMeta)xmp;

            // Return an empty result if the array does not exist,
            // hurl if it isn't the right form.
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, arrayName);
            var arrayNode = XmpNodeUtils.FindNode(xmpImpl.GetRoot(), arrayPath, false, null);

            if (arrayNode == null)
            {
                return(string.Empty);
            }
            if (!arrayNode.Options.IsArray || arrayNode.Options.IsArrayAlternate)
            {
                throw new XmpException("Named property must be non-alternate array", XmpErrorCode.BadParam);
            }

            // Make sure the separator is OK.
            CheckSeparator(separator);

            // Make sure the open and close quotes are a legitimate pair.
            var openQuote  = quotes[0];
            var closeQuote = CheckQuotes(quotes, openQuote);

            // Build the result, quoting the array items, adding separators.
            // Hurl if any item isn't simple.
            var catenatedString = new StringBuilder();

            for (var it = arrayNode.IterateChildren(); it.HasNext();)
            {
                var currItem = (XmpNode)it.Next();

                if (currItem.Options.IsCompositeProperty)
                {
                    throw new XmpException("Array items must be simple", XmpErrorCode.BadParam);
                }

                var str = ApplyQuotes(currItem.Value, openQuote, closeQuote, allowCommas);
                catenatedString.Append(str);

                if (it.HasNext())
                {
                    catenatedString.Append(separator);
                }
            }
            return(catenatedString.ToString());
        }
        /// <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);

            var xmpImpl = (XmpMeta)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", XmpErrorCode.BadParam);
                }

                var expPath  = XmpPathParser.ExpandXPath(schemaNs, propName);
                var propNode = XmpNodeUtils.FindNode(xmpImpl.GetRoot(), expPath, false, null);
                if (propNode != null)
                {
                    if (doAllProperties || !Utils.IsInternalProperty(expPath.GetSegment(XmpPath.StepSchema).Name, expPath.GetSegment(XmpPath.StepRootProp).Name))
                    {
                        var parent = propNode.Parent;
                        parent.RemoveChild(propNode);
                        if (parent.Options.IsSchemaNode && !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;
                    var schemaNode = XmpNodeUtils.FindSchemaNode(xmpImpl.GetRoot(), schemaNs, false);

                    if (schemaNode != null && 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.
                        foreach (var info in XmpMetaFactory.SchemaRegistry.FindAliases(schemaNs))
                        {
                            var path       = XmpPathParser.ExpandXPath(info.Namespace, info.PropName);
                            var actualProp = XmpNodeUtils.FindNode(xmpImpl.GetRoot(), path, false, null);
                            if (actualProp != null)
                            {
                                actualProp.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 (var it = xmpImpl.GetRoot().IterateChildren(); it.HasNext();)
                    {
                        var schema = (XmpNode)it.Next();
                        if (RemoveSchemaChildren(schema, doAllProperties))
                        {
                            it.Remove();
                        }
                    }
                }
            }
        }
Exemple #14
0
        /// <exception cref="XmpException"/>
        public 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);
            var arrayPath = XmpPathParser.ExpandXPath(schemaNs, altTextName);
            // Find the array node and set the options if it was just created.
            var arrayNode = XmpNodeUtils.FindNode(_tree, arrayPath, true, new PropertyOptions(PropertyOptions.ArrayFlag | PropertyOptions.ArrayOrderedFlag | PropertyOptions.ArrayAlternateFlag | PropertyOptions.ArrayAltTextFlag));

            if (arrayNode == null)
            {
                throw new XmpException("Failed to find or create array node", XmpErrorCode.BadXPath);
            }
            if (!arrayNode.Options.IsArrayAltText)
            {
                if (!arrayNode.HasChildren && arrayNode.Options.IsArrayAlternate)
                {
                    arrayNode.Options.IsArrayAltText = true;
                }
                else
                {
                    throw new XmpException("Specified property is no alt-text array", XmpErrorCode.BadXPath);
                }
            }
            // Make sure the x-default item, if any, is first.
            var     haveXDefault = false;
            XmpNode xdItem       = null;

            for (var it = arrayNode.IterateChildren(); it.HasNext();)
            {
                var currItem = (XmpNode)it.Next();
                if (!currItem.HasQualifier || !XmpConstants.XmlLang.Equals(currItem.GetQualifier(1).Name))
                {
                    throw new XmpException("Language qualifier must be first", XmpErrorCode.BadXPath);
                }
                if (XmpConstants.XDefault.Equals(currItem.GetQualifier(1).Value))
                {
                    xdItem       = currItem;
                    haveXDefault = true;
                    break;
                }
            }
            // Moves x-default to the beginning of the array
            if (xdItem != null && arrayNode.GetChildrenLength() > 1)
            {
                arrayNode.RemoveChild(xdItem);
                arrayNode.AddChild(1, xdItem);
            }
            // Find the appropriate item.
            // chooseLocalizedText will make sure the array is a language
            // alternative.
            var result           = XmpNodeUtils.ChooseLocalizedText(arrayNode, genericLang, specificLang);
            var match            = (int)result[0];
            var itemNode         = (XmpNode)result[1];
            var specificXDefault = XmpConstants.XDefault.Equals(specificLang);

            switch (match)
            {
            case XmpNodeUtils.CltNoValues:
            {
                // Create the array items for the specificLang and x-default, with
                // x-default first.
                XmpNodeUtils.AppendLangItem(arrayNode, XmpConstants.XDefault, itemValue);
                haveXDefault = true;
                if (!specificXDefault)
                {
                    XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue);
                }
                break;
            }

            case XmpNodeUtils.CltSpecificMatch:
            {
                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);
                    for (var it1 = arrayNode.IterateChildren(); it1.HasNext();)
                    {
                        var currItem = (XmpNode)it1.Next();
                        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.CltSingleGeneric:
            {
                // 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.CltMultipleGeneric:
            {
                // Create the specific language, ignore x-default.
                XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue);
                if (specificXDefault)
                {
                    haveXDefault = true;
                }
                break;
            }

            case XmpNodeUtils.CltXdefault:
            {
                // Create the specific language, update x-default if it was the only
                // item.
                if (xdItem != null && arrayNode.GetChildrenLength() == 1)
                {
                    xdItem.Value = itemValue;
                }
                XmpNodeUtils.AppendLangItem(arrayNode, specificLang, itemValue);
                break;
            }

            case XmpNodeUtils.CltFirstItem:
            {
                // 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", XmpErrorCode.InternalFailure);
            }
            }
            // Add an x-default at the front if needed.
            if (!haveXDefault && arrayNode.GetChildrenLength() == 1)
            {
                XmpNodeUtils.AppendLangItem(arrayNode, XmpConstants.XDefault, itemValue);
            }
        }