/// <summary>
        /// Constructor with optionsl initial values. If <code>propName</code> is provided,
        /// <code>schemaNs</code> has also be provided. </summary>
        /// <param name="xmp"> the iterated metadata object. </param>
        /// <param name="schemaNs"> the iteration is reduced to this schema (optional) </param>
        /// <param name="propPath"> the iteration is redurce to this property within the <code>schemaNs</code> </param>
        /// <param name="options"> advanced iteration options, see <seealso cref="IteratorOptions"/> </param>
        /// <exception cref="XmpException"> If the node defined by the paramters is not existing.  </exception>
        public XmpIteratorImpl(XmpMetaImpl xmp, string schemaNs, string propPath, IteratorOptions options)
        {
            // make sure that options is defined at least with defaults
            _options = options ?? new IteratorOptions();

            // the start node of the iteration depending on the schema and property filter
            XmpNode startNode;
            string  initialPath  = null;
            bool    baseSchema   = !String.IsNullOrEmpty(schemaNs);
            bool    baseProperty = !String.IsNullOrEmpty(propPath);

            if (!baseSchema && !baseProperty)
            {
                // complete tree will be iterated
                startNode = xmp.Root;
            }
            else if (baseSchema && baseProperty)
            {
                // Schema and property node provided
                XmpPath path = XmpPathParser.ExpandXPath(schemaNs, propPath);

                // base path is the prop path without the property leaf
                XmpPath basePath = new XmpPath();
                for (int i = 0; i < path.Size() - 1; i++)
                {
                    basePath.Add(path.GetSegment(i));
                }

                startNode   = XmpNodeUtils.FindNode(xmp.Root, path, false, null);
                _baseNs     = schemaNs;
                initialPath = basePath.ToString();
            }
            else if (baseSchema && !baseProperty)
            {
                // Only Schema provided
                startNode = XmpNodeUtils.FindSchemaNode(xmp.Root, schemaNs, false);
            }
            else // !baseSchema  &&  baseProperty
            {
                // No schema but property provided -> error
                throw new XmpException("Schema namespace URI is required", XmpError.BADSCHEMA);
            }


            // create iterator
            if (startNode != null)
            {
                _nodeIterator = (!_options.JustChildren)
                                    ? new NodeIterator(this, startNode, initialPath, 1)
                                    : new NodeIteratorChildren(this, startNode, initialPath);
            }
            else
            {
                // create null iterator
                _nodeIterator = EmptyList.GetEnumerator();
            }
        }
Example #2
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();
        }
        /// <summary>
        /// Parses the root node of an XMP Path, checks if namespace and prefix fit together
        /// and resolve the property to the base property if it is an alias.
        /// </summary>
        /// <param name="schemaNs">the root namespace</param>
        /// <param name="pos">the parsing position helper</param>
        /// <param name="expandedXPath">the path to contribute to</param>
        /// <exception cref="XmpException">If the path is not valid.</exception>
        private static void ParseRootNode(string schemaNs, PathPosition pos, XmpPath expandedXPath)
        {
            while (pos.StepEnd < pos.Path.Length && "/[*".IndexOf(pos.Path[pos.StepEnd]) < 0)
            {
                pos.StepEnd++;
            }

            if (pos.StepEnd == pos.StepBegin)
            {
                throw new XmpException("Empty initial XMPPath step", XmpErrorCode.BadXPath);
            }

            var rootProp  = VerifyXPathRoot(schemaNs, pos.Path.Substring(pos.StepBegin, pos.StepEnd - pos.StepBegin));
            var aliasInfo = XmpMetaFactory.SchemaRegistry.FindAlias(rootProp);

            if (aliasInfo == null)
            {
                // add schema xpath step
                expandedXPath.Add(new XmpPathSegment(schemaNs, XmpPathStepType.SchemaNode));
                var rootStep = new XmpPathSegment(rootProp, XmpPathStepType.StructFieldStep);
                expandedXPath.Add(rootStep);
                return;
            }

            // add schema xpath step and base step of alias
            expandedXPath.Add(new XmpPathSegment(aliasInfo.Namespace, XmpPathStepType.SchemaNode));
            expandedXPath.Add(new XmpPathSegment(VerifyXPathRoot(aliasInfo.Namespace, aliasInfo.PropName), XmpPathStepType.StructFieldStep)
            {
                IsAlias   = true,
                AliasForm = aliasInfo.AliasForm.GetOptions()
            });

            if (aliasInfo.AliasForm.IsArrayAltText)
            {
                expandedXPath.Add(new XmpPathSegment("[?xml:lang='x-default']", XmpPathStepType.QualSelectorStep)
                {
                    IsAlias   = true,
                    AliasForm = aliasInfo.AliasForm.GetOptions()
                });
            }
            else if (aliasInfo.AliasForm.IsArray)
            {
                expandedXPath.Add(new XmpPathSegment("[1]", XmpPathStepType.ArrayIndexStep)
                {
                    IsAlias   = true,
                    AliasForm = aliasInfo.AliasForm.GetOptions()
                });
            }
        }
Example #4
0
        /// <summary>
        /// Split an XMPPath expression apart at the conceptual steps, adding the
        /// root namespace prefix to the first property component.
        /// </summary>
        /// <remarks>
        /// The schema URI is put in the first (0th) slot in the expanded XMPPath.
        /// Check if the top level component is an alias, but don't resolve it.
        /// <para />
        /// In the most verbose case steps are separated by '/', and each step can be
        /// of these forms:
        /// <list>
        ///   <item>
        ///     <term>prefix:name</term>
        ///     <description>A top level property or struct field.</description>
        ///   </item>
        ///   <item>
        ///     <term>[index]</term>
        ///     <description>An element of an array.</description>
        ///   </item>
        ///   <item>
        ///     <term>[last()]</term>
        ///     <description>The last element of an array.</description>
        ///   </item>
        ///   <item>
        ///     <term>[fieldName=&quot;value&quot;]</term>
        ///     <description>An element in an array of structs, chosen by a field value.</description>
        ///   </item>
        ///   <item>
        ///     <term>[@xml:lang=&quot;value&quot;]</term>
        ///     <description>An element in an alt-text array, chosen by the xml:lang qualifier.</description>
        ///   </item>
        ///   <item>
        ///     <term>[?qualName=&quot;value&quot;]</term>
        ///     <description>An element in an array, chosen by a qualifier value.</description>
        ///   </item>
        ///   <item>
        ///     <term>@xml:lang</term>
        ///     <description>An xml:lang qualifier.</description>
        ///   </item>
        ///   <item>
        ///     <term>?qualName</term>
        ///     <description>A general qualifier.</description>
        ///   </item>
        /// </list>
        /// <para />
        /// The logic is complicated though by shorthand for arrays, the separating
        /// '/' and leading '*' are optional. These are all equivalent: array/*[2]
        /// array/[2] array*[2] array[2] All of these are broken into the 2 steps
        /// "array" and "[2]".
        /// <para />
        /// The value portion in the array selector forms is a string quoted by '''
        /// or '"'. The value may contain any character including a doubled quoting
        /// character. The value may be empty.
        /// <para />
        /// The syntax isn't checked, but an XML name begins with a letter or '_',
        /// and contains letters, digits, '.', '-', '_', and a bunch of special
        /// non-ASCII Unicode characters. An XML qualified name is a pair of names
        /// separated by a colon.
        /// </remarks>
        /// <param name="schemaNs">schema namespace</param>
        /// <param name="path">property name</param>
        /// <returns>Returns the expandet XMPPath.</returns>
        /// <exception cref="XmpException">Thrown if the format is not correct somehow.</exception>
        public static XmpPath ExpandXPath(string schemaNs, string path)
        {
            if (schemaNs == null || path == null)
            {
                throw new XmpException("Parameter must not be null", XmpErrorCode.BadParam);
            }

            var expandedXPath = new XmpPath();
            var pos           = new PathPosition {
                Path = path
            };

            // Pull out the first component and do some special processing on it: add the schema
            // namespace prefix and and see if it is an alias. The start must be a "qualName".
            ParseRootNode(schemaNs, pos, expandedXPath);

            // Now continue to process the rest of the XMPPath string.
            while (pos.StepEnd < path.Length)
            {
                pos.StepBegin = pos.StepEnd;

                SkipPathDelimiter(path, pos);

                pos.StepEnd = pos.StepBegin;
                var segment = path[pos.StepBegin] != '['
                    ? ParseStructSegment(pos)
                    : ParseIndexSegment(pos);

                if (segment.Kind == XmpPath.StructFieldStep)
                {
                    if (segment.Name[0] == '@')
                    {
                        segment.Name = "?" + segment.Name.Substring(1);
                        if (!"?xml:lang".Equals(segment.Name))
                        {
                            throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath);
                        }
                    }

                    if (segment.Name[0] == '?')
                    {
                        pos.NameStart++;
                        segment.Kind = XmpPath.QualifierStep;
                    }

                    VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart));
                }
                else if (segment.Kind == XmpPath.FieldSelectorStep)
                {
                    if (segment.Name[1] == '@')
                    {
                        segment.Name = "[?" + segment.Name.Substring(2);

                        if (!segment.Name.StartsWith("[?xml:lang="))
                        {
                            throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath);
                        }
                    }

                    if (segment.Name[1] == '?')
                    {
                        pos.NameStart++;
                        segment.Kind = XmpPath.QualSelectorStep;
                        VerifyQualName(pos.Path.Substring(pos.NameStart, pos.NameEnd - pos.NameStart));
                    }
                }

                expandedXPath.Add(segment);
            }

            return(expandedXPath);
        }
Example #5
0
        /// <summary>
        /// Split an XMPPath expression apart at the conceptual steps, adding the
        /// root namespace prefix to the first property component.
        /// </summary>
        /// <remarks>
        /// The schema URI is put in the first (0th) slot in the expanded XMPPath.
        /// Check if the top level component is an alias, but don't resolve it.
        /// <para />
        /// In the most verbose case steps are separated by '/', and each step can be
        /// of these forms:
        /// <list>
        ///   <item>
        ///     <term>prefix:name</term>
        ///     <description>A top level property or struct field.</description>
        ///   </item>
        ///   <item>
        ///     <term>[index]</term>
        ///     <description>An element of an array.</description>
        ///   </item>
        ///   <item>
        ///     <term>[last()]</term>
        ///     <description>The last element of an array.</description>
        ///   </item>
        ///   <item>
        ///     <term>[fieldName=&quot;value&quot;]</term>
        ///     <description>An element in an array of structs, chosen by a field value.</description>
        ///   </item>
        ///   <item>
        ///     <term>[@xml:lang=&quot;value&quot;]</term>
        ///     <description>An element in an alt-text array, chosen by the xml:lang qualifier.</description>
        ///   </item>
        ///   <item>
        ///     <term>[?qualName=&quot;value&quot;]</term>
        ///     <description>An element in an array, chosen by a qualifier value.</description>
        ///   </item>
        ///   <item>
        ///     <term>@xml:lang</term>
        ///     <description>An xml:lang qualifier.</description>
        ///   </item>
        ///   <item>
        ///     <term>?qualName</term>
        ///     <description>A general qualifier.</description>
        ///   </item>
        /// </list>
        /// <para />
        /// The logic is complicated though by shorthand for arrays, the separating
        /// '/' and leading '*' are optional. These are all equivalent: array/*[2]
        /// array/[2] array*[2] array[2] All of these are broken into the 2 steps
        /// "array" and "[2]".
        /// <para />
        /// The value portion in the array selector forms is a string quoted by '''
        /// or '"'. The value may contain any character including a doubled quoting
        /// character. The value may be empty.
        /// <para />
        /// The syntax isn't checked, but an XML name begins with a letter or '_',
        /// and contains letters, digits, '.', '-', '_', and a bunch of special
        /// non-ASCII Unicode characters. An XML qualified name is a pair of names
        /// separated by a colon.
        /// </remarks>
        /// <param name="schemaNs">schema namespace</param>
        /// <param name="path">property name</param>
        /// <returns>Returns the expandet XMPPath.</returns>
        /// <exception cref="XmpException">Thrown if the format is not correct somehow.</exception>
        public static XmpPath ExpandXPath(string schemaNs, string path)
        {
            if (schemaNs == null || path == null)
                throw new XmpException("Parameter must not be null", XmpErrorCode.BadParam);

            var expandedXPath = new XmpPath();
            var pos = new PathPosition { Path = path };

            // Pull out the first component and do some special processing on it: add the schema
            // namespace prefix and and see if it is an alias. The start must be a "qualName".
            ParseRootNode(schemaNs, pos, expandedXPath);

            // Now continue to process the rest of the XMPPath string.
            while (pos.StepEnd < path.Length)
            {
                pos.StepBegin = pos.StepEnd;

                SkipPathDelimiter(path, pos);

                pos.StepEnd = pos.StepBegin;
                var segment = path[pos.StepBegin] != '['
                    ? ParseStructSegment(pos)
                    : ParseIndexSegment(pos);

                if (segment.Kind == XmpPath.StructFieldStep)
                {
                    if (segment.Name[0] == '@')
                    {
                        segment.Name = "?" + segment.Name.Substring (1);
                        if (!"?xml:lang".Equals(segment.Name))
                            throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath);
                    }

                    if (segment.Name[0] == '?')
                    {
                        pos.NameStart++;
                        segment.Kind = XmpPath.QualifierStep;
                    }

                    VerifyQualName(pos.Path.Substring (pos.NameStart, pos.NameEnd - pos.NameStart));
                }
                else if (segment.Kind == XmpPath.FieldSelectorStep)
                {
                    if (segment.Name[1] == '@')
                    {
                        segment.Name = "[?" + segment.Name.Substring (2);

                        if (!segment.Name.StartsWith("[?xml:lang="))
                            throw new XmpException("Only xml:lang allowed with '@'", XmpErrorCode.BadXPath);
                    }

                    if (segment.Name[1] == '?')
                    {
                        pos.NameStart++;
                        segment.Kind = XmpPath.QualSelectorStep;
                        VerifyQualName(pos.Path.Substring (pos.NameStart, pos.NameEnd - pos.NameStart));
                    }
                }

                expandedXPath.Add(segment);
            }

            return expandedXPath;
        }
Example #6
0
        /// <summary>
        /// Parses the root node of an XMP Path, checks if namespace and prefix fit together
        /// and resolve the property to the base property if it is an alias.
        /// </summary>
        /// <param name="schemaNs">the root namespace</param>
        /// <param name="pos">the parsing position helper</param>
        /// <param name="expandedXPath">the path to contribute to</param>
        /// <exception cref="XmpException">If the path is not valid.</exception>
        private static void ParseRootNode(string schemaNs, PathPosition pos, XmpPath expandedXPath)
        {
            while (pos.StepEnd < pos.Path.Length && "/[*".IndexOf(pos.Path[pos.StepEnd]) < 0)
                pos.StepEnd++;

            if (pos.StepEnd == pos.StepBegin)
                throw new XmpException("Empty initial XMPPath step", XmpErrorCode.BadXPath);

            var rootProp = VerifyXPathRoot(schemaNs, pos.Path.Substring (pos.StepBegin, pos.StepEnd - pos.StepBegin));
            var aliasInfo = XmpMetaFactory.SchemaRegistry.FindAlias(rootProp);

            if (aliasInfo == null)
            {
                // add schema xpath step
                expandedXPath.Add(new XmpPathSegment(schemaNs, XmpPath.SchemaNode));
                var rootStep = new XmpPathSegment(rootProp, XmpPath.StructFieldStep);
                expandedXPath.Add(rootStep);
            }
            else
            {
                // add schema xpath step and base step of alias
                expandedXPath.Add(new XmpPathSegment(aliasInfo.Namespace, XmpPath.SchemaNode));
                var rootStep = new XmpPathSegment(VerifyXPathRoot(aliasInfo.Namespace, aliasInfo.PropName), XmpPath.StructFieldStep);
                rootStep.IsAlias = true;
                rootStep.AliasForm = aliasInfo.AliasForm.GetOptions();
                expandedXPath.Add(rootStep);

                if (aliasInfo.AliasForm.IsArrayAltText)
                {
                    var qualSelectorStep = new XmpPathSegment("[?xml:lang='x-default']", XmpPath.QualSelectorStep);
                    qualSelectorStep.IsAlias = true;
                    qualSelectorStep.AliasForm = aliasInfo.AliasForm.GetOptions();
                    expandedXPath.Add(qualSelectorStep);
                }
                else if (aliasInfo.AliasForm.IsArray)
                {
                    var indexStep = new XmpPathSegment("[1]", XmpPath.ArrayIndexStep);
                    indexStep.IsAlias = true;
                    indexStep.AliasForm = aliasInfo.AliasForm.GetOptions();
                    expandedXPath.Add(indexStep);
                }
            }
        }