Example #1
0
        /// <summary>Locate or create the item node and set the value.</summary>
        /// <remarks>
        /// Locate or create the item node and set the value. Note the index
        /// parameter is one-based! The index can be in the range [1..size + 1] or
        /// "last()", normalize it and check the insert flags. The order of the
        /// normalization checks is important. If the array is empty we end up with
        /// an index and location to set item size + 1.
        /// </remarks>
        /// <param name="arrayNode">an array node</param>
        /// <param name="itemIndex">the index where to insert the item</param>
        /// <param name="itemValue">the item value</param>
        /// <param name="itemOptions">the options for the new item</param>
        /// <param name="insert">insert oder overwrite at index position?</param>
        /// <exception cref="XmpException"/>
        private void DoSetArrayItem(XmpNode arrayNode, int itemIndex, string itemValue, PropertyOptions itemOptions, bool insert)
        {
            var itemNode = new XmpNode(XmpConstants.ArrayItemName, null);

            itemOptions = XmpNodeUtils.VerifySetOptions(itemOptions, itemValue);
            // in insert mode the index after the last is allowed,
            // even ARRAY_LAST_ITEM points to the index *after* the last.
            var maxIndex = insert ? arrayNode.GetChildrenLength() + 1 : arrayNode.GetChildrenLength();

            if (itemIndex == XmpConstants.ArrayLastItem)
            {
                itemIndex = maxIndex;
            }
            if (1 <= itemIndex && itemIndex <= maxIndex)
            {
                if (!insert)
                {
                    arrayNode.RemoveChild(itemIndex);
                }
                arrayNode.AddChild(itemIndex, itemNode);
                SetNode(itemNode, itemValue, itemOptions, false);
            }
            else
            {
                throw new XmpException("Array index out of bounds", XmpErrorCode.BadIndex);
            }
        }
Example #2
0
 /// <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);
             }
         }
     }
 }
Example #3
0
        /// <summary>Fixes the GPS Timestamp in EXIF.</summary>
        /// <param name="exifSchema">the EXIF schema node</param>
        /// <exception cref="XmpException">Thrown if the date conversion fails.</exception>
        private static void FixGpsTimeStamp(XmpNode exifSchema)
        {
            // Note: if dates are not found the convert-methods throws an exceptions,
            //          and this methods returns.
            var gpsDateTime = XmpNodeUtils.FindChildNode(exifSchema, "exif:GPSTimeStamp", false);

            if (gpsDateTime == null)
            {
                return;
            }

            try
            {
                var binGpsStamp = XmpCore.XmpUtils.ConvertToDate(gpsDateTime.Value);
                if (binGpsStamp.Year != 0 || binGpsStamp.Month != 0 || binGpsStamp.Day != 0)
                {
                    return;
                }

                var otherDate = XmpNodeUtils.FindChildNode(exifSchema, "exif:DateTimeOriginal", false)
                                ?? XmpNodeUtils.FindChildNode(exifSchema, "exif:DateTimeDigitized", false);

                var binOtherDate = XmpCore.XmpUtils.ConvertToDate(otherDate.Value);
                var cal          = binGpsStamp.Calendar;
                cal.Set(CalendarEnum.Year, binOtherDate.Year);
                cal.Set(CalendarEnum.Month, binOtherDate.Month);
                cal.Set(CalendarEnum.DayOfMonth, binOtherDate.Day);
                binGpsStamp       = new XmpDateTime(cal);
                gpsDateTime.Value = XmpCore.XmpUtils.ConvertFromDate(binGpsStamp);
            }
            catch (XmpException)
            {
                // Don't let a missing or bad date stop other things.
            }
        }
Example #4
0
        /// <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);
        }
Example #5
0
        /// <summary>
        /// The internals for setProperty() and related calls, used after the node is
        /// found or created.
        /// </summary>
        /// <param name="node">the newly created node</param>
        /// <param name="value">the node value, can be <c>null</c></param>
        /// <param name="newOptions">options for the new node, must not be <c>null</c>.</param>
        /// <param name="deleteExisting">flag if the existing value is to be overwritten</param>
        /// <exception cref="XmpException">thrown if options and value do not correspond</exception>
        internal static void SetNode(XmpNode node, object value, PropertyOptions newOptions, bool deleteExisting)
        {
            if (deleteExisting)
            {
                node.Clear();
            }

            // its checked by setOptions(), if the merged result is a valid options set
            node.Options.MergeWith(newOptions);

            if (!node.Options.IsCompositeProperty)
            {
                // This is setting the value of a leaf node.
                XmpNodeUtils.SetNodeValue(node, value);
            }
            else
            {
                if (value != null && value.ToString().Length > 0)
                {
                    throw new XmpException("Composite nodes can't have values", XmpErrorCode.BadXPath);
                }

                node.RemoveChildren();
            }
        }
Example #6
0
        /// <summary>Visit all schemas to do general fixes and handle special cases.</summary>
        /// <param name="xmp">the metadata object implementation</param>
        /// <exception cref="XmpException">Thrown if the normalisation fails.</exception>
        private static void TouchUpDataModel(XmpMeta xmp)
        {
            // make sure the DC schema is existing, because it might be needed within the normalization
            // if not touched it will be removed by removeEmptySchemas
            XmpNodeUtils.FindSchemaNode(xmp.GetRoot(), XmpConstants.NsDC, true);

            // Do the special case fixes within each schema.
            for (var it = xmp.GetRoot().IterateChildren(); it.HasNext();)
            {
                var currSchema = (XmpNode)it.Next();

                switch (currSchema.Name)
                {
                case XmpConstants.NsDC:
                {
                    NormalizeDcArrays(currSchema);
                    break;
                }

                case XmpConstants.NsExif:
                {
                    // Do a special case fix for exif:GPSTimeStamp.
                    FixGpsTimeStamp(currSchema);
                    var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "exif:UserComment", false);
                    if (arrayNode != null)
                    {
                        RepairAltText(arrayNode);
                    }
                    break;
                }

                case XmpConstants.NsDm:
                {
                    // Do a special case migration of xmpDM:copyright to
                    // dc:rights['x-default'].
                    var dmCopyright = XmpNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false);
                    if (dmCopyright != null)
                    {
                        MigrateAudioCopyright(xmp, dmCopyright);
                    }
                    break;
                }

                case XmpConstants.NsXmpRights:
                {
                    var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false);
                    if (arrayNode != null)
                    {
                        RepairAltText(arrayNode);
                    }
                    break;
                }
                }
            }
        }
Example #7
0
 /// <summary>
 /// The initial support for WAV files mapped a legacy ID3 audio copyright
 /// into a new xmpDM:copyright property.
 /// </summary>
 /// <remarks>
 /// The initial support for WAV files mapped a legacy ID3 audio copyright
 /// into a new xmpDM:copyright property. This is special case code to migrate
 /// that into dc:rights['x-default']. The rules:
 /// <pre>
 /// 1. If there is no dc:rights array, or an empty array -
 /// Create one with dc:rights['x-default'] set from double linefeed and xmpDM:copyright.
 /// 2. If there is a dc:rights array but it has no x-default item -
 /// Create an x-default item as a copy of the first item then apply rule #3.
 /// 3. If there is a dc:rights array with an x-default item,
 /// Look for a double linefeed in the value.
 /// A. If no double linefeed, compare the x-default value to the xmpDM:copyright value.
 /// A1. If they match then leave the x-default value alone.
 /// A2. Otherwise, append a double linefeed and
 /// the xmpDM:copyright value to the x-default value.
 /// B. If there is a double linefeed, compare the trailing text to the xmpDM:copyright value.
 /// B1. If they match then leave the x-default value alone.
 /// B2. Otherwise, replace the trailing x-default text with the xmpDM:copyright value.
 /// 4. In all cases, delete the xmpDM:copyright property.
 /// </pre>
 /// </remarks>
 /// <param name="xmp">the metadata object</param>
 /// <param name="dmCopyright">the "dm:copyright"-property</param>
 private static void MigrateAudioCopyright(IXmpMeta xmp, XmpNode dmCopyright)
 {
     try
     {
         var dcSchema      = XmpNodeUtils.FindSchemaNode(((XmpMeta)xmp).GetRoot(), XmpConstants.NsDC, true);
         var dmValue       = dmCopyright.Value;
         var doubleLf      = "\n\n";
         var dcRightsArray = XmpNodeUtils.FindChildNode(dcSchema, "dc:rights", false);
         if (dcRightsArray == null || !dcRightsArray.HasChildren)
         {
             // 1. No dc:rights array, create from double linefeed and xmpDM:copyright.
             dmValue = doubleLf + dmValue;
             xmp.SetLocalizedText(XmpConstants.NsDC, "rights", string.Empty, XmpConstants.XDefault, dmValue, null);
         }
         else
         {
             var xdIndex = XmpNodeUtils.LookupLanguageItem(dcRightsArray, XmpConstants.XDefault);
             if (xdIndex < 0)
             {
                 // 2. No x-default item, create from the first item.
                 var firstValue = dcRightsArray.GetChild(1).Value;
                 xmp.SetLocalizedText(XmpConstants.NsDC, "rights", string.Empty, XmpConstants.XDefault, firstValue, null);
                 xdIndex = XmpNodeUtils.LookupLanguageItem(dcRightsArray, XmpConstants.XDefault);
             }
             // 3. Look for a double linefeed in the x-default value.
             var defaultNode  = dcRightsArray.GetChild(xdIndex);
             var defaultValue = defaultNode.Value;
             var lfPos        = defaultValue.IndexOf(doubleLf);
             if (lfPos < 0)
             {
                 // 3A. No double LF, compare whole values.
                 if (!dmValue.Equals(defaultValue))
                 {
                     // 3A2. Append the xmpDM:copyright to the x-default
                     // item.
                     defaultNode.Value = defaultValue + doubleLf + dmValue;
                 }
             }
             else
             {
                 // 3B. Has double LF, compare the tail.
                 if (!defaultValue.Substring(lfPos + 2).Equals(dmValue))
                 {
                     // 3B2. Replace the x-default tail.
                     defaultNode.Value = defaultValue.Substring(0, lfPos + 2 - 0) + dmValue;
                 }
             }
         }
         // 4. Get rid of the xmpDM:copyright.
         dmCopyright.Parent.RemoveChild(dmCopyright);
     }
     catch (XmpException)
     {
     }
 }
Example #8
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();
        }
Example #9
0
        /// <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);
        }
Example #10
0
 /// <summary>Serializes an array property.</summary>
 /// <param name="node">an XMPNode</param>
 /// <param name="indent">the current indent level</param>
 /// <exception cref="System.IO.IOException">Forwards the writer exceptions.</exception>
 /// <exception cref="XmpException">If qualifier and element fields are mixed.</exception>
 private void SerializeCompactRdfArrayProp(XmpNode node, int indent)
 {
     // This is an array.
     Write('>');
     WriteNewline();
     EmitRdfArrayTag(node, true, indent + 1);
     if (node.Options.IsArrayAltText)
     {
         XmpNodeUtils.NormalizeLangArray(node);
     }
     SerializeCompactRdfElementProps(node, indent + 2);
     EmitRdfArrayTag(node, false, indent + 1);
 }
Example #11
0
 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);
     }
 }
Example #12
0
        /// <summary>Associates an alias name with an actual name.</summary>
        /// <remarks>
        /// Associates an alias name with an actual name.
        /// <para />
        /// Define a alias mapping from one namespace/property to another. Both
        /// property names must be simple names. An alias can be a direct mapping,
        /// where the alias and actual have the same data type. It is also possible
        /// to map a simple alias to an item in an array. This can either be to the
        /// first item in the array, or to the 'x-default' item in an alt-text array.
        /// Multiple alias names may map to the same actual, as long as the forms
        /// match. It is a no-op to reregister an alias in an identical fashion.
        /// Note: This method is not locking because only called by registerStandardAliases
        /// which is only called by the constructor.
        /// Note2: The method is only package-private so that it can be tested with unittests
        /// </remarks>
        /// <param name="aliasNs">The namespace URI for the alias. Must not be null or the empty string.</param>
        /// <param name="aliasProp">The name of the alias. Must be a simple name, not null or the empty string and not a general path expression.</param>
        /// <param name="actualNs">The namespace URI for the actual. Must not be null or the empty string.</param>
        /// <param name="actualProp">The name of the actual. Must be a simple name, not null or the empty string and not a general path expression.</param>
        /// <param name="aliasForm">Provides options for aliases for simple aliases to array items. This is needed to know what kind of array to create if
        /// set for the first time via the simple alias. Pass <c>XMP_NoOptions</c>, the default value, for all direct aliases regardless of whether the actual
        /// data type is an array or not (see <see cref="AliasOptions"/>).</param>
        /// <exception cref="XmpException">for inconsistant aliases.</exception>
        private void RegisterAlias(string aliasNs, string aliasProp, string actualNs, string actualProp, AliasOptions aliasForm)
        {
            lock (_lock)
            {
                ParameterAsserts.AssertSchemaNs(aliasNs);
                ParameterAsserts.AssertPropName(aliasProp);
                ParameterAsserts.AssertSchemaNs(actualNs);
                ParameterAsserts.AssertPropName(actualProp);

                // FfF: if we need the decoration with [1] or
                // FfF: [?xml:lang="x-default"] for array forms

                // Fix the alias options
                var aliasOpts = aliasForm != null ? new AliasOptions(XmpNodeUtils.VerifySetOptions(aliasForm.ToPropertyOptions(), null).GetOptions()) : new AliasOptions();
                if (_p.IsMatch(aliasProp) || _p.IsMatch(actualProp))
                {
                    throw new XmpException("Alias and actual property names must be simple", XmpErrorCode.BadXPath);
                }

                // check if both namespaces are registered
                var aliasPrefix  = GetNamespacePrefix(aliasNs);
                var actualPrefix = GetNamespacePrefix(actualNs);

                if (aliasPrefix == null)
                {
                    throw new XmpException("Alias namespace is not registered", XmpErrorCode.BadSchema);
                }
                if (actualPrefix == null)
                {
                    throw new XmpException("Actual namespace is not registered", XmpErrorCode.BadSchema);
                }

                var key = aliasPrefix + aliasProp;

                // check if alias is already existing
                if (_aliasMap.ContainsKey(key))
                {
                    throw new XmpException("Alias is already existing", XmpErrorCode.BadParam);
                }
                if (_aliasMap.ContainsKey(actualPrefix + actualProp))
                {
                    throw new XmpException("Actual property is already an alias, use the base property", XmpErrorCode.BadParam);
                }

                _aliasMap[key] = new XmpAliasInfo(actualNs, actualPrefix, actualProp, aliasOpts);
            }
        }
Example #13
0
        /// <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);
        }
Example #14
0
 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)
     {
     }
 }
Example #15
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);
        }
Example #16
0
        /// <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);
            }
        }
Example #17
0
        /// <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);
            }
        }
Example #18
0
        /// <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);
        }
Example #19
0
        /// <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);

            var src  = (XmpMeta)source;
            var dest = (XmpMeta)destination;

            for (var it = src.GetRoot().IterateChildren(); it.HasNext();)
            {
                var sourceSchema = (XmpNode)it.Next();

                // Make sure we have a destination schema node
                var destSchema    = XmpNodeUtils.FindSchemaNode(dest.GetRoot(), sourceSchema.Name, false);
                var createdSchema = false;

                if (destSchema == null)
                {
                    destSchema = new XmpNode(sourceSchema.Name, sourceSchema.Value, new PropertyOptions {
                        IsSchemaNode = true
                    });
                    dest.GetRoot().AddChild(destSchema);
                    createdSchema = true;
                }

                // Process the source schema's children.
                for (var ic = sourceSchema.IterateChildren(); ic.HasNext();)
                {
                    var sourceProp = (XmpNode)ic.Next();
                    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.GetRoot().RemoveChild(destSchema);
                }
            }
        }
Example #20
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);
            }
        }
Example #21
0
        /// <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();
                        }
                    }
                }
            }
        }
Example #22
0
        /// <summary>Recursively handles the "value" for a node.</summary>
        /// <remarks>
        /// Recursively handles the "value" for a node. It does not matter if it is a
        /// top level property, a field of a struct, or an item of an array. The
        /// indent is that for the property element. An xml:lang qualifier is written
        /// as an attribute of the property start tag, not by itself forcing the
        /// qualified property form. The patterns below mostly ignore attribute
        /// qualifiers like xml:lang. Except for the one struct case, attribute
        /// qualifiers don't affect the output form.
        /// <code>
        /// &lt;ns:UnqualifiedSimpleProperty&gt;value&lt;/ns:UnqualifiedSimpleProperty&gt;
        /// &lt;ns:UnqualifiedStructProperty&gt; (If no rdf:resource qualifier)
        /// &lt;rdf:Description&gt;
        /// ... Fields, same forms as top level properties
        /// &lt;/rdf:Description&gt;
        /// &lt;/ns:UnqualifiedStructProperty&gt;
        /// &lt;ns:ResourceStructProperty rdf:resource=&quot;URI&quot;
        /// ... Fields as attributes
        /// &gt;
        /// &lt;ns:UnqualifiedArrayProperty&gt;
        /// &lt;rdf:Bag&gt; or Seq or Alt
        /// ... Array items as rdf:li elements, same forms as top level properties
        /// &lt;/rdf:Bag&gt;
        /// &lt;/ns:UnqualifiedArrayProperty&gt;
        /// &lt;ns:QualifiedProperty&gt;
        /// &lt;rdf:Description&gt;
        /// &lt;rdf:value&gt; ... Property &quot;value&quot; following the unqualified
        /// forms ... &lt;/rdf:value&gt;
        /// ... Qualifiers looking like named struct fields
        /// &lt;/rdf:Description&gt;
        /// &lt;/ns:QualifiedProperty&gt;
        /// </code>
        /// </remarks>
        /// <param name="node">the property node</param>
        /// <param name="emitAsRdfValue">property shall be rendered as attribute rather than tag</param>
        /// <param name="useCanonicalRdf">
        /// use canonical form with inner description tag or
        /// the compact form with rdf:ParseType=&quot;resource&quot; attribute.
        /// </param>
        /// <param name="indent">the current indent level</param>
        /// <exception cref="System.IO.IOException">Forwards all writer exceptions.</exception>
        /// <exception cref="XmpException">If &quot;rdf:resource&quot; and general qualifiers are mixed.</exception>
        private void SerializeCanonicalRdfProperty(XmpNode node, bool useCanonicalRdf, bool emitAsRdfValue, int indent)
        {
            var emitEndTag   = true;
            var indentEndTag = true;

            // Determine the XML element name. Open the start tag with the name and
            // attribute qualifiers.
            var elemName = node.Name;

            if (emitAsRdfValue)
            {
                elemName = "rdf:value";
            }
            else if (elemName == XmpConstants.ArrayItemName)
            {
                elemName = XmpConstants.RdfLi;
            }

            WriteIndent(indent);
            Write('<');
            Write(elemName);
            var hasGeneralQualifiers = false;
            var hasRdfResourceQual   = false;

            for (var it = node.IterateQualifier(); it.HasNext();)
            {
                var qualifier = (XmpNode)it.Next();
                if (!RdfAttrQualifier.Contains(qualifier.Name))
                {
                    hasGeneralQualifiers = true;
                }
                else
                {
                    hasRdfResourceQual = qualifier.Name == "rdf:resource";
                    if (!emitAsRdfValue)
                    {
                        Write(' ');
                        Write(qualifier.Name);
                        Write("=\"");
                        AppendNodeValue(qualifier.Value, true);
                        Write('"');
                    }
                }
            }
            // Process the property according to the standard patterns.
            if (hasGeneralQualifiers && !emitAsRdfValue)
            {
                // This node has general, non-attribute, qualifiers. Emit using the
                // qualified property form.
                // ! The value is output by a recursive call ON THE SAME NODE with
                // emitAsRDFValue set.
                if (hasRdfResourceQual)
                {
                    throw new XmpException("Can't mix rdf:resource and general qualifiers", XmpErrorCode.BadRdf);
                }

                // Change serialization to canonical format with inner rdf:Description-tag
                // depending on option
                if (useCanonicalRdf)
                {
                    Write(">");
                    WriteNewline();
                    indent++;
                    WriteIndent(indent);
                    Write(RdfStructStart);
                    Write(">");
                }
                else
                {
                    Write(" rdf:parseType=\"Resource\">");
                }

                WriteNewline();
                SerializeCanonicalRdfProperty(node, useCanonicalRdf, true, indent + 1);

                for (var it = node.IterateQualifier(); it.HasNext();)
                {
                    var qualifier = (XmpNode)it.Next();
                    if (!RdfAttrQualifier.Contains(qualifier.Name))
                    {
                        SerializeCanonicalRdfProperty(qualifier, useCanonicalRdf, false, indent + 1);
                    }
                }

                if (useCanonicalRdf)
                {
                    WriteIndent(indent);
                    Write(RdfStructEnd);
                    WriteNewline();
                    indent--;
                }
            }
            else
            {
                // This node has no general qualifiers. Emit using an unqualified form.
                if (!node.Options.IsCompositeProperty)
                {
                    // This is a simple property.
                    if (node.Options.IsUri)
                    {
                        Write(" rdf:resource=\"");
                        AppendNodeValue(node.Value, true);
                        Write("\"/>");
                        WriteNewline();
                        emitEndTag = false;
                    }
                    else if (string.IsNullOrEmpty(node.Value))
                    {
                        Write("/>");
                        WriteNewline();
                        emitEndTag = false;
                    }
                    else
                    {
                        Write('>');
                        AppendNodeValue(node.Value, false);
                        indentEndTag = false;
                    }
                }
                else
                {
                    if (node.Options.IsArray)
                    {
                        // This is an array.
                        Write('>');
                        WriteNewline();
                        EmitRdfArrayTag(node, true, indent + 1);

                        if (node.Options.IsArrayAltText)
                        {
                            XmpNodeUtils.NormalizeLangArray(node);
                        }

                        for (var it1 = node.IterateChildren(); it1.HasNext();)
                        {
                            var child = (XmpNode)it1.Next();
                            SerializeCanonicalRdfProperty(child, useCanonicalRdf, false, indent + 2);
                        }

                        EmitRdfArrayTag(node, false, indent + 1);
                    }
                    else if (!hasRdfResourceQual)
                    {
                        // This is a "normal" struct, use the rdf:parseType="Resource" form.
                        if (!node.HasChildren)
                        {
                            // Change serialization to canonical format with inner rdf:Description-tag
                            // if option is set
                            if (useCanonicalRdf)
                            {
                                Write(">");
                                WriteNewline();
                                WriteIndent(indent + 1);
                                Write(RdfEmptyStruct);
                            }
                            else
                            {
                                Write(" rdf:parseType=\"Resource\"/>");
                                emitEndTag = false;
                            }

                            WriteNewline();
                        }
                        else
                        {
                            // Change serialization to canonical format with inner rdf:Description-tag
                            // if option is set
                            if (useCanonicalRdf)
                            {
                                Write(">");
                                WriteNewline();
                                indent++;
                                WriteIndent(indent);
                                Write(RdfStructStart);
                                Write(">");
                            }
                            else
                            {
                                Write(" rdf:parseType=\"Resource\">");
                            }

                            WriteNewline();
                            for (var it = node.IterateChildren(); it.HasNext();)
                            {
                                var child = (XmpNode)it.Next();
                                SerializeCanonicalRdfProperty(child, useCanonicalRdf, false, indent + 1);
                            }

                            if (useCanonicalRdf)
                            {
                                WriteIndent(indent);
                                Write(RdfStructEnd);
                                WriteNewline();
                                indent--;
                            }
                        }
                    }
                    else
                    {
                        // This is a struct with an rdf:resource attribute, use the
                        // "empty property element" form.
                        for (var it1 = node.IterateChildren(); it1.HasNext();)
                        {
                            var child = (XmpNode)it1.Next();
                            if (!CanBeRdfAttrProp(child))
                            {
                                throw new XmpException("Can't mix rdf:resource and complex fields", XmpErrorCode.BadRdf);
                            }

                            WriteNewline();
                            WriteIndent(indent + 1);
                            Write(' ');
                            Write(child.Name);
                            Write("=\"");
                            AppendNodeValue(child.Value, true);
                            Write('"');
                        }

                        Write("/>");
                        WriteNewline();
                        emitEndTag = false;
                    }
                }
            }
            // Emit the property element end tag.
            if (emitEndTag)
            {
                if (indentEndTag)
                {
                    WriteIndent(indent);
                }
                Write("</");
                Write(elemName);
                Write('>');
                WriteNewline();
            }
        }
Example #23
0
        /// <summary>Visit all of the top level nodes looking for aliases.</summary>
        /// <remarks>
        /// Visit all of the top level nodes looking for aliases. If there is
        /// no base, transplant the alias subtree. If there is a base and strict
        /// aliasing is on, make sure the alias and base subtrees match.
        /// </remarks>
        /// <param name="tree">the root of the metadata tree</param>
        /// <param name="options">th parsing options</param>
        /// <exception cref="XmpException">Forwards XMP errors</exception>
        private static void MoveExplicitAliases(XmpNode tree, ParseOptions options)
        {
            if (!tree.HasAliases)
            {
                return;
            }
            tree.HasAliases = false;
            var strictAliasing = options.StrictAliasing;

            for (var schemaIt = tree.GetUnmodifiableChildren().Iterator(); schemaIt.HasNext();)
            {
                var currSchema = (XmpNode)schemaIt.Next();
                if (!currSchema.HasAliases)
                {
                    continue;
                }
                for (var propertyIt = currSchema.IterateChildren(); propertyIt.HasNext();)
                {
                    var currProp = (XmpNode)propertyIt.Next();
                    if (!currProp.IsAlias)
                    {
                        continue;
                    }
                    currProp.IsAlias = false;
                    // Find the base path, look for the base schema and root node.
                    var info = XmpMetaFactory.SchemaRegistry.FindAlias(currProp.Name);
                    if (info != null)
                    {
                        // find or create schema
                        var baseSchema = XmpNodeUtils.FindSchemaNode(tree, info.Namespace, null, true);
                        baseSchema.IsImplicit = false;
                        var baseNode = XmpNodeUtils.FindChildNode(baseSchema, info.Prefix + info.PropName, false);
                        if (baseNode == null)
                        {
                            if (info.AliasForm.IsSimple())
                            {
                                // A top-to-top alias, transplant the property.
                                // change the alias property name to the base name
                                var qname = info.Prefix + info.PropName;
                                currProp.Name = qname;
                                baseSchema.AddChild(currProp);
                                // remove the alias property
                                propertyIt.Remove();
                            }
                            else
                            {
                                // An alias to an array item,
                                // create the array and transplant the property.
                                baseNode = new XmpNode(info.Prefix + info.PropName, info.AliasForm.ToPropertyOptions());
                                baseSchema.AddChild(baseNode);
                                TransplantArrayItemAlias(propertyIt, currProp, baseNode);
                            }
                        }
                        else if (info.AliasForm.IsSimple())
                        {
                            // The base node does exist and this is a top-to-top alias.
                            // Check for conflicts if strict aliasing is on.
                            // Remove and delete the alias subtree.
                            if (strictAliasing)
                            {
                                CompareAliasedSubtrees(currProp, baseNode, true);
                            }
                            propertyIt.Remove();
                        }
                        else
                        {
                            // This is an alias to an array item and the array exists.
                            // Look for the aliased item.
                            // Then transplant or check & delete as appropriate.
                            XmpNode itemNode = null;
                            if (info.AliasForm.IsArrayAltText)
                            {
                                var xdIndex = XmpNodeUtils.LookupLanguageItem(baseNode, XmpConstants.XDefault);
                                if (xdIndex != -1)
                                {
                                    itemNode = baseNode.GetChild(xdIndex);
                                }
                            }
                            else if (baseNode.HasChildren)
                            {
                                itemNode = baseNode.GetChild(1);
                            }

                            if (itemNode == null)
                            {
                                TransplantArrayItemAlias(propertyIt, currProp, baseNode);
                            }
                            else if (strictAliasing)
                            {
                                CompareAliasedSubtrees(currProp, itemNode, true);
                            }
                            propertyIt.Remove();
                        }
                    }
                }
                currSchema.HasAliases = false;
            }
        }
Example #24
0
        /// <summary>Visit all schemas to do general fixes and handle special cases.</summary>
        /// <param name="xmp">the metadata object implementation</param>
        /// <exception cref="XmpException">Thrown if the normalisation fails.</exception>
        private static void TouchUpDataModel(XmpMeta xmp)
        {
            // make sure the DC schema is existing, because it might be needed within the normalization
            // if not touched it will be removed by removeEmptySchemas
            XmpNodeUtils.FindSchemaNode(xmp.GetRoot(), XmpConstants.NsDC, true);

            // Do the special case fixes within each schema.
            for (var it = xmp.GetRoot().IterateChildren(); it.HasNext();)
            {
                var currSchema = (XmpNode)it.Next();

                switch (currSchema.Name)
                {
                case XmpConstants.NsDC:
                {
                    NormalizeDcArrays(currSchema);
                    break;
                }

                case XmpConstants.NsExif:
                {
                    // Do a special case fix for exif:GPSTimeStamp.
                    FixGpsTimeStamp(currSchema);

                    /*var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "exif:UserComment", false);
                     * if (arrayNode != null)
                     * {
                     *  RepairAltText(arrayNode);
                     * }*/
                    var userComment = XmpNodeUtils.FindChildNode(currSchema, "exif:UserComment", false);
                    if (userComment != null)
                    {
                        if (userComment.Options.IsSimple)
                        {
                            XmpNode newNode = new XmpNode(XmpConstants.ArrayItemName, userComment.Value, userComment.Options);
                            newNode.Parent = userComment;

                            int QualNo = userComment.GetQualifierLength();
                            while (QualNo > 0)
                            {
                                newNode.AddQualifier(userComment.GetQualifier(userComment.GetQualifierLength() - QualNo));
                                --QualNo;
                            }

                            userComment.RemoveQualifiers();
                            if (!newNode.Options.HasLanguage)
                            {
                                var po = new PropertyOptions();
                                po.SetOption(PropertyOptions.HasQualifiersFlag, true);
                                XmpNode langQual = new XmpNode("xml:lang", "x-default", po);
                                newNode.AddQualifier(langQual);
                                newNode.Options.SetOption(PropertyOptions.HasQualifiersFlag, true);
                                newNode.Options.SetOption(PropertyOptions.HasLanguageFlag, true);
                            }
                            userComment.AddChild(newNode);
                            userComment.Options = new PropertyOptions(PropertyOptions.ArrayFlag | PropertyOptions.ArrayOrderedFlag
                                                                      | PropertyOptions.ArrayAltTextFlag | PropertyOptions.ArrayAlternateFlag);
                            userComment.Value = "";
                        }
                        RepairAltText(userComment);
                    }

                    break;
                }

                case XmpConstants.NsDm:
                {
                    // Do a special case migration of xmpDM:copyright to
                    // dc:rights['x-default'].
                    var dmCopyright = XmpNodeUtils.FindChildNode(currSchema, "xmpDM:copyright", false);
                    if (dmCopyright != null)
                    {
                        MigrateAudioCopyright(xmp, dmCopyright);
                    }
                    break;
                }

                case XmpConstants.NsXmpRights:
                {
                    var arrayNode = XmpNodeUtils.FindChildNode(currSchema, "xmpRights:UsageTerms", false);
                    if (arrayNode != null)
                    {
                        RepairAltText(arrayNode);
                    }
                    break;
                }
                }
            }
        }
Example #25
0
        /// <param name="destXmp">The destination XMP object.</param>
        /// <param name="sourceNode">the source node</param>
        /// <param name="destParent">the parent of the destination node</param>
        /// <param name="replaceOldValues">Replace the values of existing properties.</param>
        /// <param name="deleteEmptyValues">flag if properties with empty values should be deleted in the destination object.</param>
        /// <exception cref="XmpException"/>
        private static void AppendSubtree(XmpMeta destXmp, XmpNode sourceNode, XmpNode destParent, bool replaceOldValues, bool deleteEmptyValues)
        {
            var destNode     = XmpNodeUtils.FindChildNode(destParent, sourceNode.Name, false);
            var valueIsEmpty = false;

            if (deleteEmptyValues)
            {
                valueIsEmpty = sourceNode.Options.IsSimple ? string.IsNullOrEmpty(sourceNode.Value) : !sourceNode.HasChildren;
            }

            if (deleteEmptyValues && valueIsEmpty)
            {
                if (destNode != null)
                {
                    destParent.RemoveChild(destNode);
                }
            }
            else
            {
                if (destNode == null)
                {
                    // The one easy case, the destination does not exist.
                    destParent.AddChild((XmpNode)sourceNode.Clone());
                }
                else
                {
                    if (replaceOldValues)
                    {
                        // The destination exists and should be replaced.
                        destXmp.SetNode(destNode, sourceNode.Value, sourceNode.Options, true);
                        destParent.RemoveChild(destNode);
                        destNode = (XmpNode)sourceNode.Clone();
                        destParent.AddChild(destNode);
                    }
                    else
                    {
                        // The destination exists and is not totally replaced. Structs and arrays are merged.
                        var sourceForm = sourceNode.Options;
                        var destForm   = destNode.Options;
                        if (sourceForm != destForm)
                        {
                            return;
                        }

                        if (sourceForm.IsStruct)
                        {
                            // To merge a struct process the fields recursively. E.g. add simple missing fields.
                            // The recursive call to AppendSubtree will handle deletion for fields with empty
                            // values.
                            for (var it = sourceNode.IterateChildren(); it.HasNext();)
                            {
                                var sourceField = (XmpNode)it.Next();
                                AppendSubtree(destXmp, sourceField, destNode, replaceOldValues, deleteEmptyValues);
                                if (deleteEmptyValues && !destNode.HasChildren)
                                {
                                    destParent.RemoveChild(destNode);
                                }
                            }
                        }
                        else if (sourceForm.IsArrayAltText)
                        {
                            // Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first.
                            // Make a special check for deletion of empty values. Meaningful in AltText arrays
                            // because the "xml:lang" qualifier provides unambiguous source/dest correspondence.
                            for (var it = sourceNode.IterateChildren(); it.HasNext();)
                            {
                                var sourceItem = (XmpNode)it.Next();

                                if (!sourceItem.HasQualifier || !XmpConstants.XmlLang.Equals(sourceItem.GetQualifier(1).Name))
                                {
                                    continue;
                                }

                                var destIndex = XmpNodeUtils.LookupLanguageItem(destNode, sourceItem.GetQualifier(1).Value);
                                if (deleteEmptyValues && string.IsNullOrEmpty(sourceItem.Value))
                                {
                                    if (destIndex != -1)
                                    {
                                        destNode.RemoveChild(destIndex);
                                        if (!destNode.HasChildren)
                                        {
                                            destParent.RemoveChild(destNode);
                                        }
                                    }
                                }
                                else if (destIndex == -1)
                                {
                                    // Not replacing, keep the existing item.
                                    if (!XmpConstants.XDefault.Equals(sourceItem.GetQualifier(1).Value) || !destNode.HasChildren)
                                    {
                                        sourceItem.CloneSubtree(destNode);
                                    }
                                    else
                                    {
                                        var destItem = new XmpNode(sourceItem.Name, sourceItem.Value, sourceItem.Options);
                                        sourceItem.CloneSubtree(destItem);
                                        destNode.AddChild(1, destItem);
                                    }
                                }
                            }
                        }
                        else if (sourceForm.IsArray)
                        {
                            // Merge other arrays by item values. Don't worry about order or duplicates. Source
                            // items with empty values do not cause deletion, that conflicts horribly with
                            // merging.
                            for (var children = sourceNode.IterateChildren(); children.HasNext();)
                            {
                                var sourceItem = (XmpNode)children.Next();

                                var match = false;
                                for (var id = destNode.IterateChildren(); id.HasNext();)
                                {
                                    var destItem = (XmpNode)id.Next();
                                    if (ItemValuesMatch(sourceItem, destItem))
                                    {
                                        match = true;
                                    }
                                }

                                if (!match)
                                {
                                    destNode = (XmpNode)sourceItem.Clone();
                                    destParent.AddChild(destNode);
                                }
                            }
                        }
                    }
                }
            }
        }
Example #26
0
        /// <summary>Compares two nodes including its children and qualifier.</summary>
        /// <param name="leftNode">an <c>XMPNode</c></param>
        /// <param name="rightNode">an <c>XMPNode</c></param>
        /// <returns>Returns true if the nodes are equal, false otherwise.</returns>
        /// <exception cref="XmpException">Forwards exceptions to the calling method.</exception>
        private static bool ItemValuesMatch(XmpNode leftNode, XmpNode rightNode)
        {
            var leftForm  = leftNode.Options;
            var rightForm = rightNode.Options;

            if (leftForm.Equals(rightForm))
            {
                return(false);
            }

            if (leftForm.GetOptions() == 0)
            {
                // Simple nodes, check the values and xml:lang qualifiers.
                if (!leftNode.Value.Equals(rightNode.Value))
                {
                    return(false);
                }
                if (leftNode.Options.HasLanguage != rightNode.Options.HasLanguage)
                {
                    return(false);
                }
                if (leftNode.Options.HasLanguage && !leftNode.GetQualifier(1).Value.Equals(rightNode.GetQualifier(1).Value))
                {
                    return(false);
                }
            }
            else
            {
                if (leftForm.IsStruct)
                {
                    // Struct nodes, see if all fields match, ignoring order.
                    if (leftNode.GetChildrenLength() != rightNode.GetChildrenLength())
                    {
                        return(false);
                    }

                    for (var it = leftNode.IterateChildren(); it.HasNext();)
                    {
                        var leftField  = (XmpNode)it.Next();
                        var rightField = XmpNodeUtils.FindChildNode(rightNode, leftField.Name, false);
                        if (rightField == null || !ItemValuesMatch(leftField, rightField))
                        {
                            return(false);
                        }
                    }
                }
                else
                {
                    // Array nodes, see if the "leftNode" values are present in the
                    // "rightNode", ignoring order, duplicates,
                    // and extra values in the rightNode-> The rightNode is the
                    // destination for AppendProperties.
                    Debug.Assert(leftForm.IsArray);
                    for (var il = leftNode.IterateChildren(); il.HasNext();)
                    {
                        var leftItem = (XmpNode)il.Next();
                        var match    = false;

                        for (var ir = rightNode.IterateChildren(); ir.HasNext();)
                        {
                            var rightItem = (XmpNode)ir.Next();
                            if (ItemValuesMatch(leftItem, rightItem))
                            {
                                match = true;
                                break;
                            }
                        }

                        if (!match)
                        {
                            return(false);
                        }
                    }
                }
            }
            return(true);
        }
Example #27
0
        /// <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());
        }