/// <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>
        /// Be called on root particle of complex type.
        /// </summary>
        /// <param name="validationContext"></param>
        /// <returns></returns>
        internal override void Validate(ValidationContext validationContext)
        {
            Debug.Assert(validationContext != null);

            OpenXmlCompositeElement element = validationContext.Element as OpenXmlCompositeElement;

            Debug.Assert(element != null);

            var child = validationContext.GetFirstChildMc();
            ValidationErrorInfo errorInfo;

            // no children
            if (child == null)
            {
                if (this.ParticleConstraint.MinOccurs == 0)
                {
                    // no child, ok
                    return;
                }
                else
                {
                    var requiredElements = this.GetRequiredElements();

                    if (requiredElements.Count > 0)
                    {
                        errorInfo = validationContext.ComposeSchemaValidationError(element, null, "Sch_IncompleteContentExpectingComplex", GetExpectedChildrenMessage(element, requiredElements));
                        validationContext.EmitError(errorInfo);
                    }
                    return;
                }
            }

            if (this._particleMatchInfo == null)
            {
                this._particleMatchInfo = new ParticleMatchInfo(child);
            }
            else
            {
                _particleMatchInfo.Reset(child);
            }

            this.TryMatch(_particleMatchInfo, validationContext);

            switch (_particleMatchInfo.Match)
            {
            case ParticleMatch.Nomatch:
                // error: can not be matched, it is invalid
                EmitInvalidElementError(validationContext, _particleMatchInfo);
                return;

            case ParticleMatch.Partial:
                EmitInvalidElementError(validationContext, _particleMatchInfo);
                return;

            case ParticleMatch.Matched:
                Debug.Assert(_particleMatchInfo.LastMatchedElement != null);
                child = validationContext.GetNextChildMc(_particleMatchInfo.LastMatchedElement);
                {
                    // Two cases now.
                    // 1. All children be matched.
                    // 2. Too many children ( > maxOccurs ).

                    if (child != null)
                    {
                        // invalid child
                        EmitInvalidElementError(validationContext, _particleMatchInfo);
                        // TODO: how can we tell the user what is the required child? Use reflection in OpenXmlElement.
                    }
                    else
                    {
                        //Debug.Assert(result.Valid == true);
                    }
                }
                break;
            }

            return;
        }
        protected virtual void EmitInvalidElementError(ValidationContext validationContext,
                                                       ParticleMatchInfo particleMatchInfo)
        {
            OpenXmlElement child;

            // re-validate the element, collect the expected children information
            if (particleMatchInfo.Match != ParticleMatch.Nomatch)
            {
#if DEBUG
                var oldParticleMatchInfo = particleMatchInfo;
                particleMatchInfo = new ParticleMatchInfo();
#endif
                child = validationContext.GetFirstChildMc();
                validationContext.CollectExpectedChildren = true;
                particleMatchInfo.Reset(child);
                particleMatchInfo.InitExpectedChildren();
                this.TryMatch(particleMatchInfo, validationContext);
                validationContext.CollectExpectedChildren = false;

#if DEBUG
                Debug.Assert(particleMatchInfo.Match == oldParticleMatchInfo.Match);
                Debug.Assert(particleMatchInfo.LastMatchedElement == oldParticleMatchInfo.LastMatchedElement);
#endif
            }

            var element = validationContext.Element;
            if (particleMatchInfo.LastMatchedElement == null)
            {
                child = validationContext.GetFirstChildMc();
            }
            else
            {
                child = validationContext.GetNextChildMc(particleMatchInfo.LastMatchedElement);
            }

            ValidationErrorInfo errorInfo;
            string expectedChildren = null;

            switch (particleMatchInfo.Match)
            {
            case ParticleMatch.Nomatch:
                expectedChildren = GetExpectedChildrenMessage(validationContext.Element, this.GetExpectedElements());
                break;

            case ParticleMatch.Partial:
                // error: the child can not be matched, it is invalid
                if (child == null)
                {
                    // missing child
                    errorInfo = validationContext.ComposeSchemaValidationError(element, null, "Sch_IncompleteContentExpectingComplex", GetExpectedChildrenMessage(element, particleMatchInfo.ExpectedChildren));
                    validationContext.EmitError(errorInfo);

                    return;
                }
                else
                {
                    expectedChildren = GetExpectedChildrenMessage(validationContext.Element, particleMatchInfo.ExpectedChildren);
                }
                break;

            case ParticleMatch.Matched:
                if (this.ParticleConstraint.CanOccursMoreThanOne)
                {
                    expectedChildren = GetExpectedChildrenMessage(validationContext.Element, this.GetExpectedElements());
                }
                else
                {
                    expectedChildren = GetExpectedChildrenMessage(validationContext.Element, particleMatchInfo.ExpectedChildren);
                }
                break;
            }

            if (validationContext.Element.CanContainsChild(child))
            {
                // The child can be contained in the parent, but not follow the schema.
                errorInfo = validationContext.ComposeSchemaValidationError(element, child, "Sch_UnexpectedElementContentExpectingComplex", child.XmlQualifiedName.ToString(), expectedChildren);
            }
            else
            {
                //Fix bug #448264, specifal case: same element name, but wrong type. Only occurs when validating memory DOM.
                var validElement = element.TryCreateValidChild(validationContext.FileFormat, child.NamespaceUri, child.LocalName);
                if (validElement == null)
                {
                    errorInfo = validationContext.ComposeSchemaValidationError(element, child, "Sch_InvalidElementContentExpectingComplex", child.XmlQualifiedName.ToString(), expectedChildren);
                }
                else
                {
                    // parent can contains a different type of element with same name
                    errorInfo = validationContext.ComposeSchemaValidationError(element, child, "Sch_InvalidElementContentWrongType", child.XmlQualifiedName.ToString(), child.GetType().Name);
                }
            }
            validationContext.EmitError(errorInfo);
        }
        /// <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;
        }
        protected virtual void EmitInvalidElementError(ValidationContext validationContext,
                                                       ParticleMatchInfo particleMatchInfo)
        {
            OpenXmlElement child;

            // re-validate the element, collect the expected children information 
            if (particleMatchInfo.Match != ParticleMatch.Nomatch)
            {
#if DEBUG
                var oldParticleMatchInfo = particleMatchInfo;
                particleMatchInfo = new ParticleMatchInfo();
#endif
                child = validationContext.GetFirstChildMc();
                validationContext.CollectExpectedChildren = true;
                particleMatchInfo.Reset(child);
                particleMatchInfo.InitExpectedChildren();
                this.TryMatch(particleMatchInfo, validationContext);
                validationContext.CollectExpectedChildren = false;

#if DEBUG
                Debug.Assert(particleMatchInfo.Match == oldParticleMatchInfo.Match);
                Debug.Assert(particleMatchInfo.LastMatchedElement == oldParticleMatchInfo.LastMatchedElement);
#endif
            }

            var element = validationContext.Element;
            if (particleMatchInfo.LastMatchedElement == null)
            {
                child = validationContext.GetFirstChildMc();
            }
            else
            {
                child = validationContext.GetNextChildMc(particleMatchInfo.LastMatchedElement);
            }

            ValidationErrorInfo errorInfo;
            string expectedChildren = null;

            switch (particleMatchInfo.Match)
            {
                case ParticleMatch.Nomatch:
                    expectedChildren = GetExpectedChildrenMessage(validationContext.Element, this.GetExpectedElements());
                    break;

                case ParticleMatch.Partial:
                    // error: the child can not be matched, it is invalid
                    if (child == null)
                    {
                        // missing child
                        errorInfo = validationContext.ComposeSchemaValidationError(element, null, "Sch_IncompleteContentExpectingComplex", GetExpectedChildrenMessage(element, particleMatchInfo.ExpectedChildren));
                        validationContext.EmitError(errorInfo);

                        return;
                    }
                    else
                    {
                        expectedChildren = GetExpectedChildrenMessage(validationContext.Element, particleMatchInfo.ExpectedChildren);
                    }
                    break;

                case ParticleMatch.Matched:
                    if (this.ParticleConstraint.CanOccursMoreThanOne)
                    {
                        expectedChildren = GetExpectedChildrenMessage(validationContext.Element, this.GetExpectedElements());
                    }
                    else
                    {
                        expectedChildren = GetExpectedChildrenMessage(validationContext.Element, particleMatchInfo.ExpectedChildren);
                    }
                    break;
            }

            if (validationContext.Element.CanContainsChild(child))
            {
                // The child can be contained in the parent, but not follow the schema.
                errorInfo = validationContext.ComposeSchemaValidationError(element, child, "Sch_UnexpectedElementContentExpectingComplex", child.XmlQualifiedName.ToString(), expectedChildren);
            }
            else
            {
                //Fix bug #448264, specifal case: same element name, but wrong type. Only occurs when validating memory DOM.
                var validElement = element.TryCreateValidChild(validationContext.FileFormat, child.NamespaceUri, child.LocalName);
                if (validElement == null)
                {
                    errorInfo = validationContext.ComposeSchemaValidationError(element, child, "Sch_InvalidElementContentExpectingComplex", child.XmlQualifiedName.ToString(), expectedChildren);
                }
                else
                {
                    // parent can contains a different type of element with same name
                    errorInfo = validationContext.ComposeSchemaValidationError(element, child, "Sch_InvalidElementContentWrongType", child.XmlQualifiedName.ToString(), child.GetType().Name);
                }
            }
            validationContext.EmitError(errorInfo);
        }
        /// <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;
            }
        }