/// <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="Com.Adobe.Xmp.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", XMPErrorConstants.Badxpath);
            }
            string       rootProp  = VerifyXPathRoot(schemaNS, Sharpen.Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd));
            XMPAliasInfo aliasInfo = XMPMetaFactory.GetSchemaRegistry().FindAlias(rootProp);

            if (aliasInfo == null)
            {
                // add schema xpath step
                expandedXPath.Add(new XMPPathSegment(schemaNS, XMPPath.SchemaNode));
                XMPPathSegment rootStep = new XMPPathSegment(rootProp, XMPPath.StructFieldStep);
                expandedXPath.Add(rootStep);
            }
            else
            {
                // add schema xpath step and base step of alias
                expandedXPath.Add(new XMPPathSegment(aliasInfo.GetNamespace(), XMPPath.SchemaNode));
                XMPPathSegment rootStep = new XMPPathSegment(VerifyXPathRoot(aliasInfo.GetNamespace(), aliasInfo.GetPropName()), XMPPath.StructFieldStep);
                rootStep.SetAlias(true);
                rootStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions());
                expandedXPath.Add(rootStep);
                if (aliasInfo.GetAliasForm().IsArrayAltText())
                {
                    XMPPathSegment qualSelectorStep = new XMPPathSegment("[?xml:lang='x-default']", XMPPath.QualSelectorStep);
                    qualSelectorStep.SetAlias(true);
                    qualSelectorStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions());
                    expandedXPath.Add(qualSelectorStep);
                }
                else
                {
                    if (aliasInfo.GetAliasForm().IsArray())
                    {
                        XMPPathSegment indexStep = new XMPPathSegment("[1]", XMPPath.ArrayIndexStep);
                        indexStep.SetAlias(true);
                        indexStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions());
                        expandedXPath.Add(indexStep);
                    }
                }
            }
        }
        // empty
        /// <summary>
        /// Split an XMPPath expression apart at the conceptual steps, adding the
        /// root namespace prefix to the first property component.
        /// </summary>
        /// <remarks>
        /// Split an XMPPath expression apart at the conceptual steps, adding the
        /// root namespace prefix to the first property component. 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.
        /// <p>
        /// In the most verbose case steps are separated by '/', and each step can be
        /// of these forms:
        /// <dl>
        /// <dt>prefix:name
        /// <dd> A top level property or struct field.
        /// <dt>[index]
        /// <dd> An element of an array.
        /// <dt>[last()]
        /// <dd> The last element of an array.
        /// <dt>[fieldName=&quot;value&quot;]
        /// <dd> An element in an array of structs, chosen by a field value.
        /// <dt>[@xml:lang=&quot;value&quot;]
        /// <dd> An element in an alt-text array, chosen by the xml:lang qualifier.
        /// <dt>[?qualName=&quot;value&quot;]
        /// <dd> An element in an array, chosen by a qualifier value.
        /// <dt>@xml:lang
        /// <dd> An xml:lang qualifier.
        /// <dt>?qualName
        /// <dd> A general qualifier.
        /// </dl>
        /// <p>
        /// 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]".
        /// <p>
        /// 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.
        /// <p>
        /// 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="Com.Adobe.Xmp.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", XMPErrorConstants.Badparam);
            }
            XMPPath      expandedXPath = new XMPPath();
            PathPosition pos           = new PathPosition();

            pos.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;
                XMPPathSegment segment;
                if (path[pos.stepBegin] != '[')
                {
                    // A struct field or qualifier.
                    segment = ParseStructSegment(pos);
                }
                else
                {
                    // One of the array forms.
                    segment = ParseIndexSegment(pos);
                }
                if (segment.GetKind() == XMPPath.StructFieldStep)
                {
                    if (segment.GetName()[0] == '@')
                    {
                        segment.SetName("?" + Sharpen.Runtime.Substring(segment.GetName(), 1));
                        if (!"?xml:lang".Equals(segment.GetName()))
                        {
                            throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath);
                        }
                    }
                    if (segment.GetName()[0] == '?')
                    {
                        pos.nameStart++;
                        segment.SetKind(XMPPath.QualifierStep);
                    }
                    VerifyQualName(Sharpen.Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd));
                }
                else
                {
                    if (segment.GetKind() == XMPPath.FieldSelectorStep)
                    {
                        if (segment.GetName()[1] == '@')
                        {
                            segment.SetName("[?" + Sharpen.Runtime.Substring(segment.GetName(), 2));
                            if (!segment.GetName().StartsWith("[?xml:lang="))
                            {
                                throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath);
                            }
                        }
                        if (segment.GetName()[1] == '?')
                        {
                            pos.nameStart++;
                            segment.SetKind(XMPPath.QualSelectorStep);
                            VerifyQualName(Sharpen.Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd));
                        }
                    }
                }
                expandedXPath.Add(segment);
            }
            return(expandedXPath);
        }
 /// <summary>Follow an expanded path expression to find or create a node.</summary>
 /// <param name="xmpTree">the node to begin the search.</param>
 /// <param name="xpath">the complete xpath</param>
 /// <param name="createNodes">
 /// flag if nodes shall be created
 /// (when called by <code>setProperty()</code>)
 /// </param>
 /// <param name="leafOptions">
 /// the options for the created leaf nodes (only when
 /// <code>createNodes == true</code>).
 /// </param>
 /// <returns>Returns the node if found or created or <code>null</code>.</returns>
 /// <exception cref="Com.Adobe.Xmp.XMPException">
 /// An exception is only thrown if an error occurred,
 /// not if a node was not found.
 /// </exception>
 internal static XMPNode FindNode(XMPNode xmpTree, XMPPath xpath, bool createNodes, PropertyOptions leafOptions)
 {
     // check if xpath is set.
     if (xpath == null || xpath.Size() == 0)
     {
         throw new XMPException("Empty XMPPath", XMPErrorConstants.Badxpath);
     }
     // Root of implicitly created subtree to possible delete it later.
     // Valid only if leaf is new.
     XMPNode rootImplicitNode = null;
     XMPNode currNode = null;
     // resolve schema step
     currNode = FindSchemaNode(xmpTree, xpath.GetSegment(XMPPath.StepSchema).GetName(), createNodes);
     if (currNode == null)
     {
         return null;
     }
     else
     {
         if (currNode.IsImplicit())
         {
             currNode.SetImplicit(false);
             // Clear the implicit node bit.
             rootImplicitNode = currNode;
         }
     }
     // Save the top most implicit node.
     // Now follow the remaining steps of the original XMPPath.
     try
     {
         for (int i = 1; i < xpath.Size(); i++)
         {
             currNode = FollowXPathStep(currNode, xpath.GetSegment(i), createNodes);
             if (currNode == null)
             {
                 if (createNodes)
                 {
                     // delete implicitly created nodes
                     DeleteNode(rootImplicitNode);
                 }
                 return null;
             }
             else
             {
                 if (currNode.IsImplicit())
                 {
                     // clear the implicit node flag
                     currNode.SetImplicit(false);
                     // if node is an ALIAS (can be only in root step, auto-create array
                     // when the path has been resolved from a not simple alias type
                     if (i == 1 && xpath.GetSegment(i).IsAlias() && xpath.GetSegment(i).GetAliasForm() != 0)
                     {
                         currNode.GetOptions().SetOption(xpath.GetSegment(i).GetAliasForm(), true);
                     }
                     else
                     {
                         // "CheckImplicitStruct" in C++
                         if (i < xpath.Size() - 1 && xpath.GetSegment(i).GetKind() == XMPPath.StructFieldStep && !currNode.GetOptions().IsCompositeProperty())
                         {
                             currNode.GetOptions().SetStruct(true);
                         }
                     }
                     if (rootImplicitNode == null)
                     {
                         rootImplicitNode = currNode;
                     }
                 }
             }
         }
     }
     catch (XMPException e)
     {
         // Save the top most implicit node.
         // if new notes have been created prior to the error, delete them
         if (rootImplicitNode != null)
         {
             DeleteNode(rootImplicitNode);
         }
         throw;
     }
     if (rootImplicitNode != null)
     {
         // set options only if a node has been successful created
         currNode.GetOptions().MergeWith(leafOptions);
         currNode.SetOptions(currNode.GetOptions());
     }
     return currNode;
 }
 /// <summary>Constructor with optionsl initial values.</summary>
 /// <remarks>
 /// Constructor with optionsl initial values. If <code>propName</code> is provided,
 /// <code>schemaNS</code> 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 redurce to this property within the <code>schemaNS</code></param>
 /// <param name="options">
 /// advanced iteration options, see
 /// <see cref="Com.Adobe.Xmp.Options.IteratorOptions"/>
 /// </param>
 /// <exception cref="Com.Adobe.Xmp.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
     this.options = options != null ? options : new IteratorOptions();
     // the start node of the iteration depending on the schema and property filter
     XMPNode startNode = null;
     string initialPath = null;
     bool baseSchema = schemaNS != null && schemaNS.Length > 0;
     bool baseProperty = propPath != null && propPath.Length > 0;
     if (!baseSchema && !baseProperty)
     {
         // complete tree will be iterated
         startNode = xmp.GetRoot();
     }
     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.GetRoot(), path, false, null);
             baseNS = 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", XMPErrorConstants.Badschema);
             }
         }
     }
     // create iterator
     if (startNode != null)
     {
         if (!this.options.IsJustChildren())
         {
             nodeIterator = new XMPIteratorImpl.NodeIterator(this, startNode, initialPath, 1);
         }
         else
         {
             nodeIterator = new XMPIteratorImpl.NodeIteratorChildren(this, startNode, initialPath);
         }
     }
     else
     {
         // create null iterator
         nodeIterator = Sharpen.Collections.EmptyList().Iterator();
     }
 }
		// empty
		/// <summary>
		/// Split an XMPPath expression apart at the conceptual steps, adding the
		/// root namespace prefix to the first property component.
		/// </summary>
		/// <remarks>
		/// Split an XMPPath expression apart at the conceptual steps, adding the
		/// root namespace prefix to the first property component. 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.
		/// <p>
		/// In the most verbose case steps are separated by '/', and each step can be
		/// of these forms:
		/// <dl>
		/// <dt>prefix:name
		/// <dd> A top level property or struct field.
		/// <dt>[index]
		/// <dd> An element of an array.
		/// <dt>[last()]
		/// <dd> The last element of an array.
		/// <dt>[fieldName=&quot;value&quot;]
		/// <dd> An element in an array of structs, chosen by a field value.
		/// <dt>[@xml:lang=&quot;value&quot;]
		/// <dd> An element in an alt-text array, chosen by the xml:lang qualifier.
		/// <dt>[?qualName=&quot;value&quot;]
		/// <dd> An element in an array, chosen by a qualifier value.
		/// <dt>@xml:lang
		/// <dd> An xml:lang qualifier.
		/// <dt>?qualName
		/// <dd> A general qualifier.
		/// </dl>
		/// <p>
		/// 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]".
		/// <p>
		/// 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.
		/// <p>
		/// 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="Com.Adobe.Xmp.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", XMPErrorConstants.Badparam);
			}
			XMPPath expandedXPath = new XMPPath();
			PathPosition pos = new PathPosition();
			pos.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;
				XMPPathSegment segment;
				if (path[pos.stepBegin] != '[')
				{
					// A struct field or qualifier.
					segment = ParseStructSegment(pos);
				}
				else
				{
					// One of the array forms.
					segment = ParseIndexSegment(pos);
				}
				if (segment.GetKind() == XMPPath.StructFieldStep)
				{
					if (segment.GetName()[0] == '@')
					{
						segment.SetName("?" + Sharpen.Runtime.Substring(segment.GetName(), 1));
						if (!"?xml:lang".Equals(segment.GetName()))
						{
							throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath);
						}
					}
					if (segment.GetName()[0] == '?')
					{
						pos.nameStart++;
						segment.SetKind(XMPPath.QualifierStep);
					}
					VerifyQualName(Sharpen.Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd));
				}
				else
				{
					if (segment.GetKind() == XMPPath.FieldSelectorStep)
					{
						if (segment.GetName()[1] == '@')
						{
							segment.SetName("[?" + Sharpen.Runtime.Substring(segment.GetName(), 2));
							if (!segment.GetName().StartsWith("[?xml:lang="))
							{
								throw new XMPException("Only xml:lang allowed with '@'", XMPErrorConstants.Badxpath);
							}
						}
						if (segment.GetName()[1] == '?')
						{
							pos.nameStart++;
							segment.SetKind(XMPPath.QualSelectorStep);
							VerifyQualName(Sharpen.Runtime.Substring(pos.path, pos.nameStart, pos.nameEnd));
						}
					}
				}
				expandedXPath.Add(segment);
			}
			return expandedXPath;
		}
		/// <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="Com.Adobe.Xmp.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", XMPErrorConstants.Badxpath);
			}
			string rootProp = VerifyXPathRoot(schemaNS, Sharpen.Runtime.Substring(pos.path, pos.stepBegin, pos.stepEnd));
			XMPAliasInfo aliasInfo = XMPMetaFactory.GetSchemaRegistry().FindAlias(rootProp);
			if (aliasInfo == null)
			{
				// add schema xpath step
				expandedXPath.Add(new XMPPathSegment(schemaNS, XMPPath.SchemaNode));
				XMPPathSegment rootStep = new XMPPathSegment(rootProp, XMPPath.StructFieldStep);
				expandedXPath.Add(rootStep);
			}
			else
			{
				// add schema xpath step and base step of alias
				expandedXPath.Add(new XMPPathSegment(aliasInfo.GetNamespace(), XMPPath.SchemaNode));
				XMPPathSegment rootStep = new XMPPathSegment(VerifyXPathRoot(aliasInfo.GetNamespace(), aliasInfo.GetPropName()), XMPPath.StructFieldStep);
				rootStep.SetAlias(true);
				rootStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions());
				expandedXPath.Add(rootStep);
				if (aliasInfo.GetAliasForm().IsArrayAltText())
				{
					XMPPathSegment qualSelectorStep = new XMPPathSegment("[?xml:lang='x-default']", XMPPath.QualSelectorStep);
					qualSelectorStep.SetAlias(true);
					qualSelectorStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions());
					expandedXPath.Add(qualSelectorStep);
				}
				else
				{
					if (aliasInfo.GetAliasForm().IsArray())
					{
						XMPPathSegment indexStep = new XMPPathSegment("[1]", XMPPath.ArrayIndexStep);
						indexStep.SetAlias(true);
						indexStep.SetAliasForm(aliasInfo.GetAliasForm().GetOptions());
						expandedXPath.Add(indexStep);
					}
				}
			}
		}