/// <summary>
        /// Try match the particle once.
        /// </summary>
        /// <param name="particleMatchInfo"></param>
        /// <param name="validationContext">The context information for validation.</param>
        public override void TryMatchOnce(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext)
        {
            Debug.Assert(!(particleMatchInfo.StartElement is OpenXmlMiscNode));
            
            var next = particleMatchInfo.StartElement;

            particleMatchInfo.LastMatchedElement = null;
            particleMatchInfo.Match = ParticleMatch.Nomatch;

            ParticleConstraint childConstraint;
            int constraintIndex = 0;
            int constraintTotal = this.ParticleConstraint.ChildrenParticles.Length;

            while (constraintIndex < constraintTotal && next != null)
            {
                childConstraint = this.ParticleConstraint.ChildrenParticles[constraintIndex];

                // Use Reset() instead of new() to avoid heavy memory alloction and GC.
                _childMatchInfo.Reset(next);

                childConstraint.ParticleValidator.TryMatch(_childMatchInfo, validationContext);

                // if the _childMatchInfo.StartElement is changed, it means this method of this object is called more than once on the stack.
                Debug.Assert(_childMatchInfo.StartElement == next);

                switch (_childMatchInfo.Match)
                {
                    case ParticleMatch.Nomatch:
                        // continue trying match next child constraint.
                        constraintIndex++;
                        break;

                    case ParticleMatch.Matched:
                        particleMatchInfo.Match = ParticleMatch.Matched;
                        particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                        return;

                    case ParticleMatch.Partial:
                        // partial match, incomplete children.
                        particleMatchInfo.Match = ParticleMatch.Partial;
                        particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                        if (validationContext.CollectExpectedChildren)
                        {
                            particleMatchInfo.SetExpectedChildren(_childMatchInfo.ExpectedChildren);
                        }
                        return;
                }
            }

            // no match
            Debug.Assert(particleMatchInfo.Match == ParticleMatch.Nomatch);
            return;
        }
        /// <summary>
        /// Try match the particle once.
        /// </summary>
        /// <param name="particleMatchInfo"></param>
        /// <param name="validationContext">The context information for validation.</param>
        public override void TryMatchOnce(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext)
        {
            Debug.Assert(!(particleMatchInfo.StartElement is OpenXmlMiscNode));

            var next = particleMatchInfo.StartElement;

            particleMatchInfo.LastMatchedElement = null;
            particleMatchInfo.Match = ParticleMatch.Nomatch;

            ParticleConstraint childConstraint;
            int constraintIndex = 0;
            int constraintTotal = this.ParticleConstraint.ChildrenParticles.Length;

            while (constraintIndex < constraintTotal && next != null)
            {
                childConstraint = this.ParticleConstraint.ChildrenParticles[constraintIndex];

                // Use Reset() instead of new() to avoid heavy memory alloction and GC.
                _childMatchInfo.Reset(next);

                childConstraint.ParticleValidator.TryMatch(_childMatchInfo, validationContext);

                // if the _childMatchInfo.StartElement is changed, it means this method of this object is called more than once on the stack.
                Debug.Assert(_childMatchInfo.StartElement == next);

                switch (_childMatchInfo.Match)
                {
                case ParticleMatch.Nomatch:
                    // continue trying match next child constraint.
                    constraintIndex++;
                    break;

                case ParticleMatch.Matched:
                    particleMatchInfo.Match = ParticleMatch.Matched;
                    particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                    return;

                case ParticleMatch.Partial:
                    // partial match, incomplete children.
                    particleMatchInfo.Match = ParticleMatch.Partial;
                    particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                    if (validationContext.CollectExpectedChildren)
                    {
                        particleMatchInfo.SetExpectedChildren(_childMatchInfo.ExpectedChildren);
                    }
                    return;
                }
            }

            // no match
            Debug.Assert(particleMatchInfo.Match == ParticleMatch.Nomatch);
            return;
        }
        /// <summary>
        /// Try match the particle.
        /// </summary>
        /// <param name="particleMatchInfo"></param>
        /// <param name="validationContext">The context information for validation.</param>
        public override void TryMatch(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext)
        {
            if (this.ParticleConstraint.MaxOccurs == 1)
            {
                this.TryMatchOnce(particleMatchInfo, validationContext);
            }
            else
            {
                int matchCount = 0;
                var next       = particleMatchInfo.StartElement;

                while (next != null && this.ParticleConstraint.MaxOccursGreaterThan(matchCount))
                {
                    // Use Reset() instead of new() to avoid heavy memory alloction and GC.
                    _childMatchInfo.Reset(next);
                    this.TryMatchOnce(_childMatchInfo, validationContext);

                    // if the _childMatchInfo.StartElement is changed, it means this method of this object is called more than once on the stack.
                    Debug.Assert(_childMatchInfo.StartElement == next);

                    if (_childMatchInfo.Match == ParticleMatch.Nomatch)
                    {
                        break;
                    }
                    else if (_childMatchInfo.Match == ParticleMatch.Matched)
                    {
                        matchCount++;
                        particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                        next = validationContext.GetNextChildMc(particleMatchInfo.LastMatchedElement);
                    }
                    else
                    {
                        // return error
                        particleMatchInfo.Match = ParticleMatch.Partial;
                        particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                        if (validationContext.CollectExpectedChildren)
                        {
                            particleMatchInfo.SetExpectedChildren(_childMatchInfo.ExpectedChildren);
                        }
                        return;
                    }
                }

                if (matchCount == 0)
                {
                    particleMatchInfo.Match = ParticleMatch.Nomatch;
                    if (validationContext.CollectExpectedChildren)
                    {
                        particleMatchInfo.SetExpectedChildren(this.GetExpectedElements());
                    }
                }
                else if (matchCount >= this.ParticleConstraint.MinOccurs)
                {
                    // matched ok
                    particleMatchInfo.Match = ParticleMatch.Matched;
                }
                else
                {
                    if (this.GetRequiredElements(particleMatchInfo.ExpectedChildren))
                    {
                        // minOccurs failed, incomplete children.
                        particleMatchInfo.Match = ParticleMatch.Partial;
                    }
                    else
                    {
                        // all children elements are optional
                        particleMatchInfo.Match = ParticleMatch.Matched;
                    }
                }
            }
            return;
        }
        /// <summary>
        /// Try match the particle once.
        /// </summary>
        /// <param name="particleMatchInfo"></param>
        /// <param name="validationContext">The context information for validation.</param>
        public override void TryMatchOnce(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext)
        {
            Debug.Assert(!(particleMatchInfo.StartElement is OpenXmlMiscNode));
            
            var next = particleMatchInfo.StartElement;

            particleMatchInfo.LastMatchedElement = null;
            particleMatchInfo.Match = ParticleMatch.Nomatch;

            ParticleConstraint childConstraint;
            int constraintIndex = 0;
            int constraintTotal = this.ParticleConstraint.ChildrenParticles.Length;

            while (constraintIndex < constraintTotal && next != null)
            {
                childConstraint = this.ParticleConstraint.ChildrenParticles[constraintIndex];

                // Use Reset() instead of new() to avoid heavy memory alloction and GC.
                _childMatchInfo.Reset(next);

                childConstraint.ParticleValidator.TryMatch(_childMatchInfo, validationContext);

                // if the _childMatchInfo.StartElement is changed, it means this method of this object is called more than once on the stack.
                Debug.Assert(_childMatchInfo.StartElement == next);

                switch (_childMatchInfo.Match)
                {
                    case ParticleMatch.Nomatch:
                        if ( childConstraint.ParticleValidator.GetRequiredElements(null))
                        {
                            if (validationContext.CollectExpectedChildren)
                            {
                                if (particleMatchInfo.ExpectedChildren == null)
                                {
                                    particleMatchInfo.SetExpectedChildren(childConstraint.ParticleValidator.GetRequiredElements());
                                }
                                else
                                {
                                    // reuse same object, avoid object alloction.
                                    particleMatchInfo.ExpectedChildren.Clear();
                                    childConstraint.ParticleValidator.GetRequiredElements(particleMatchInfo.ExpectedChildren);
                                }
                            }

                            // incomplete children.
                            if (next == particleMatchInfo.StartElement)
                            {
                                // the first child is not the correct one.
                                particleMatchInfo.Match = ParticleMatch.Nomatch;
                                particleMatchInfo.LastMatchedElement = null;
                                return;
                            }
                            else
                            {
                                // partial match, incomplete children.
                                particleMatchInfo.Match = ParticleMatch.Partial;
                                return;
                            }
                        }
                        else
                        {
                            // continue trying match next child constraint.
                            constraintIndex++;
                            continue;
                        }


                    case ParticleMatch.Matched:
                        particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                        next = validationContext.GetNextChildMc(particleMatchInfo.LastMatchedElement);
                        // continue trying match next child constraint.
                        constraintIndex++;
                        break;

                    case ParticleMatch.Partial:
                        // partial match, incomplete children.
                        particleMatchInfo.Match = ParticleMatch.Partial;
                        particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                        if (validationContext.CollectExpectedChildren)
                        {
                            particleMatchInfo.SetExpectedChildren(_childMatchInfo.ExpectedChildren);
                        }
                        return;
                }
            }

            if (constraintIndex == constraintTotal)
            {
                if (particleMatchInfo.LastMatchedElement != null)
                {
                    particleMatchInfo.Match = ParticleMatch.Matched;
                }
                else
                {
                    particleMatchInfo.Match = ParticleMatch.Nomatch;
                }
                return;
            }
            else
            {
                for (; constraintIndex < constraintTotal; constraintIndex++)
                {
                    if (this.ParticleConstraint.ChildrenParticles[constraintIndex].ParticleValidator.GetRequiredElements(null))
                    {
                        if (validationContext.CollectExpectedChildren)
                        {
                            if (particleMatchInfo.ExpectedChildren == null)
                            {
                                particleMatchInfo.InitExpectedChildren();
                            }
                            this.ParticleConstraint.ChildrenParticles[constraintIndex].ParticleValidator.GetRequiredElements(particleMatchInfo.ExpectedChildren);
                        }
                        particleMatchInfo.Match = ParticleMatch.Partial;
                        return;
                    }
                }
                // all other children constraint are optional.
                particleMatchInfo.Match = ParticleMatch.Matched;
                return;
            }
        }
        /// <summary>
        /// Try match the particle.
        /// </summary>
        /// <param name="particleMatchInfo"></param>
        /// <param name="validationContext">The context information for validation.</param>
        public override void TryMatch(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext)
        {
            if (this.ParticleConstraint.MaxOccurs == 1)
            {
                this.TryMatchOnce(particleMatchInfo, validationContext);
            }
            else
            {
                int matchCount = 0;
                var next = particleMatchInfo.StartElement;

                while (next != null && this.ParticleConstraint.MaxOccursGreaterThan(matchCount))
                {
                    // Use Reset() instead of new() to avoid heavy memory alloction and GC.
                    _childMatchInfo.Reset(next);
                    this.TryMatchOnce(_childMatchInfo, validationContext);

                    // if the _childMatchInfo.StartElement is changed, it means this method of this object is called more than once on the stack.
                    Debug.Assert(_childMatchInfo.StartElement == next);

                    if (_childMatchInfo.Match == ParticleMatch.Nomatch)
                    {
                        break;
                    }
                    else if (_childMatchInfo.Match == ParticleMatch.Matched)
                    {
                        matchCount++;
                        particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                        next = validationContext.GetNextChildMc(particleMatchInfo.LastMatchedElement);
                    }
                    else
                    {
                        // return error
                        particleMatchInfo.Match = ParticleMatch.Partial;
                        particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                        if (validationContext.CollectExpectedChildren)
                        {
                            particleMatchInfo.SetExpectedChildren(_childMatchInfo.ExpectedChildren);
                        }
                        return;
                    }
                }

                if (matchCount == 0)
                {
                    particleMatchInfo.Match = ParticleMatch.Nomatch;
                    if (validationContext.CollectExpectedChildren)
                    {
                        particleMatchInfo.SetExpectedChildren(this.GetExpectedElements());
                    }
                }
                else if (matchCount >= this.ParticleConstraint.MinOccurs)
                {
                    // matched ok
                    particleMatchInfo.Match = ParticleMatch.Matched;
                }
                else
                {
                    if (this.GetRequiredElements(particleMatchInfo.ExpectedChildren))
                    {
                        // minOccurs failed, incomplete children.
                        particleMatchInfo.Match = ParticleMatch.Partial;
                    }
                    else
                    {
                        // all children elements are optional
                        particleMatchInfo.Match = ParticleMatch.Matched;
                    }
                }
            }
            return;
        }
        /// <summary>
        /// Try match the particle once.
        /// </summary>
        /// <param name="particleMatchInfo"></param>
        /// <param name="validationContext">The context information for validation.</param>
        public override void TryMatchOnce(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext)
        {
            Debug.Assert(!(particleMatchInfo.StartElement is OpenXmlMiscNode));

            var next = particleMatchInfo.StartElement;

            particleMatchInfo.LastMatchedElement = null;
            particleMatchInfo.Match = ParticleMatch.Nomatch;

            ParticleConstraint childConstraint;
            int constraintIndex = 0;
            int constraintTotal = this.ParticleConstraint.ChildrenParticles.Length;

            while (constraintIndex < constraintTotal && next != null)
            {
                childConstraint = this.ParticleConstraint.ChildrenParticles[constraintIndex];

                // Use Reset() instead of new() to avoid heavy memory alloction and GC.
                _childMatchInfo.Reset(next);

                childConstraint.ParticleValidator.TryMatch(_childMatchInfo, validationContext);

                // if the _childMatchInfo.StartElement is changed, it means this method of this object is called more than once on the stack.
                Debug.Assert(_childMatchInfo.StartElement == next);

                switch (_childMatchInfo.Match)
                {
                case ParticleMatch.Nomatch:
                    if (childConstraint.ParticleValidator.GetRequiredElements(null))
                    {
                        if (validationContext.CollectExpectedChildren)
                        {
                            if (particleMatchInfo.ExpectedChildren == null)
                            {
                                particleMatchInfo.SetExpectedChildren(childConstraint.ParticleValidator.GetRequiredElements());
                            }
                            else
                            {
                                // reuse same object, avoid object alloction.
                                particleMatchInfo.ExpectedChildren.Clear();
                                childConstraint.ParticleValidator.GetRequiredElements(particleMatchInfo.ExpectedChildren);
                            }
                        }

                        // incomplete children.
                        if (next == particleMatchInfo.StartElement)
                        {
                            // the first child is not the correct one.
                            particleMatchInfo.Match = ParticleMatch.Nomatch;
                            particleMatchInfo.LastMatchedElement = null;
                            return;
                        }
                        else
                        {
                            // partial match, incomplete children.
                            particleMatchInfo.Match = ParticleMatch.Partial;
                            return;
                        }
                    }
                    else
                    {
                        // continue trying match next child constraint.
                        constraintIndex++;
                        continue;
                    }


                case ParticleMatch.Matched:
                    particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                    next = validationContext.GetNextChildMc(particleMatchInfo.LastMatchedElement);
                    // continue trying match next child constraint.
                    constraintIndex++;
                    break;

                case ParticleMatch.Partial:
                    // partial match, incomplete children.
                    particleMatchInfo.Match = ParticleMatch.Partial;
                    particleMatchInfo.LastMatchedElement = _childMatchInfo.LastMatchedElement;
                    if (validationContext.CollectExpectedChildren)
                    {
                        particleMatchInfo.SetExpectedChildren(_childMatchInfo.ExpectedChildren);
                    }
                    return;
                }
            }

            if (constraintIndex == constraintTotal)
            {
                if (particleMatchInfo.LastMatchedElement != null)
                {
                    particleMatchInfo.Match = ParticleMatch.Matched;
                }
                else
                {
                    particleMatchInfo.Match = ParticleMatch.Nomatch;
                }
                return;
            }
            else
            {
                for (; constraintIndex < constraintTotal; constraintIndex++)
                {
                    if (this.ParticleConstraint.ChildrenParticles[constraintIndex].ParticleValidator.GetRequiredElements(null))
                    {
                        if (validationContext.CollectExpectedChildren)
                        {
                            if (particleMatchInfo.ExpectedChildren == null)
                            {
                                particleMatchInfo.InitExpectedChildren();
                            }
                            this.ParticleConstraint.ChildrenParticles[constraintIndex].ParticleValidator.GetRequiredElements(particleMatchInfo.ExpectedChildren);
                        }
                        particleMatchInfo.Match = ParticleMatch.Partial;
                        return;
                    }
                }
                // all other children constraint are optional.
                particleMatchInfo.Match = ParticleMatch.Matched;
                return;
            }
        }