/// <summary>
        /// Try match this element.
        /// </summary>
        /// <param name="particleMatchInfo"></param>
        /// <param name="validationContext"></param>
        public void TryMatch(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext)
        {
            Debug.Assert(particleMatchInfo != null);
            Debug.Assert(particleMatchInfo.StartElement != null);

            if (this.ElementId != particleMatchInfo.StartElement.ElementTypeId)
            {
                particleMatchInfo.Match = ParticleMatch.Nomatch;
            }
            else if (this.MaxOccurs == 1)
            {
                // matched element once.
                particleMatchInfo.Match = ParticleMatch.Matched;
                particleMatchInfo.LastMatchedElement = particleMatchInfo.StartElement;
            }
            else
            {
                // try to match multiple elements.
                var element = particleMatchInfo.StartElement;
                int count   = 0;

                while (element != null &&
                       this.MaxOccursGreaterThan(count) &&
                       element.ElementTypeId == this.ElementId)
                {
                    count++;
                    particleMatchInfo.LastMatchedElement = element;
                    element = validationContext.GetNextChildMc(element);
                }

                if (count >= this.MinOccurs)
                {
                    particleMatchInfo.Match = ParticleMatch.Matched;
                }
                else
                {
                    particleMatchInfo.Match = ParticleMatch.Partial;
                    if (validationContext.CollectExpectedChildren)
                    {
                        if (particleMatchInfo.ExpectedChildren == null)
                        {
                            particleMatchInfo.InitExpectedChildren();
                        }
                        particleMatchInfo.ExpectedChildren.Add(this.ElementId);
                    }
                }
            }
            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>
        /// <remarks>
        /// xsd:all can only contain xsd:element children and maxOccurs of each children can only be 1
        /// </remarks>
        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;


            foreach (var childParticle in this.ParticleConstraint.ChildrenParticles)
            {
                this._childrenParticles[childParticle.ElementId] = false;
            }

            bool visited;

            while (next != null)
            {
                if (this._childrenParticles.TryGetValue(next.ElementTypeId, out visited))
                {
                    if (visited)
                    {
                        // error, maxOccurs > 1
                        break;
                    }
                    else
                    {
                        this._childrenParticles[next.ElementTypeId] = true;
                    }
                }
                else
                {
                    // error, other child not allowed
                    break;
                }

                particleMatchInfo.LastMatchedElement = next;

                next = validationContext.GetNextChildMc(next);
            }

            if (particleMatchInfo.ExpectedChildren == null)
            {
                particleMatchInfo.InitExpectedChildren();
            }

            if (particleMatchInfo.LastMatchedElement == null)
            {
                Debug.Assert(next == particleMatchInfo.StartElement);
                particleMatchInfo.Match = ParticleMatch.Nomatch;

                foreach (var childParticle in this.ParticleConstraint.ChildrenParticles)
                {
                    Debug.Assert(childParticle is ElementParticle);

                    particleMatchInfo.ExpectedChildren.Add(childParticle.ElementId);
                }

                return;
            }
            else
            {
                particleMatchInfo.Match = ParticleMatch.Matched;

                // check if matched
                foreach (var childParticle in this.ParticleConstraint.ChildrenParticles)
                {
                    Debug.Assert(childParticle is ElementParticle);
                    if (!this._childrenParticles[childParticle.ElementId] && childParticle.MinOccurs == 1)
                    {
                        // one of the required children are missed.
                        particleMatchInfo.Match = ParticleMatch.Partial;
                    }
                }

                // find expected child elements.
                foreach (var childParticle in this.ParticleConstraint.ChildrenParticles)
                {
                    if (!this._childrenParticles[childParticle.ElementId])
                    {
                        particleMatchInfo.ExpectedChildren.Add(childParticle.ElementId);
                    }
                }
                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 this element.
        /// </summary>
        /// <param name="particleMatchInfo"></param>
        /// <param name="validationContext"></param>
        public void TryMatch(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext)
        {
            Debug.Assert(particleMatchInfo != null);
            Debug.Assert(particleMatchInfo.StartElement != null);

            if (this.ElementId != particleMatchInfo.StartElement.ElementTypeId)
            {
                particleMatchInfo.Match = ParticleMatch.Nomatch;
            }
            else if (this.MaxOccurs == 1)
            {
                // matched element once.
                particleMatchInfo.Match = ParticleMatch.Matched;
                particleMatchInfo.LastMatchedElement = particleMatchInfo.StartElement;
            }
            else
            {
                // try to match multiple elements.
                var element = particleMatchInfo.StartElement;
                int count = 0;

                while (element != null &&
                    this.MaxOccursGreaterThan(count) &&
                    element.ElementTypeId == this.ElementId)
                {
                    count++;
                    particleMatchInfo.LastMatchedElement = element;
                    element = validationContext.GetNextChildMc(element);
                }

                if (count >= this.MinOccurs)
                {
                    particleMatchInfo.Match = ParticleMatch.Matched;
                }
                else
                {
                    particleMatchInfo.Match = ParticleMatch.Partial;
                    if (validationContext.CollectExpectedChildren)
                    {
                        if (particleMatchInfo.ExpectedChildren == null)
                        {
                            particleMatchInfo.InitExpectedChildren();
                        }
                        particleMatchInfo.ExpectedChildren.Add(this.ElementId);
                    }
                }
            }
            return;
        }
        /// <summary>
        /// Try match the particle once.
        /// </summary>
        /// <param name="particleMatchInfo"></param>
        /// <param name="validationContext">The context information for validation.</param>
        /// <remarks>
        /// xsd:all can only contain xsd:element children and maxOccurs of each children can only be 1
        /// </remarks>
        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;


            foreach (var childParticle in this.ParticleConstraint.ChildrenParticles)
            {
                this._childrenParticles[childParticle.ElementId] = false;
            }

            bool visited;

            while (next != null)
            {
                if (this._childrenParticles.TryGetValue(next.ElementTypeId, out visited))
                {
                    if (visited)
                    {
                        // error, maxOccurs > 1
                        break;
                    }
                    else
                    {
                        this._childrenParticles[next.ElementTypeId] = true;
                    }
                }
                else
                {
                    // error, other child not allowed
                    break;
                }

                particleMatchInfo.LastMatchedElement = next;

                next = validationContext.GetNextChildMc(next);
            }

            if (particleMatchInfo.ExpectedChildren == null)
            {
                particleMatchInfo.InitExpectedChildren();
            }

            if (particleMatchInfo.LastMatchedElement == null)
            {
                Debug.Assert(next == particleMatchInfo.StartElement);
                particleMatchInfo.Match = ParticleMatch.Nomatch;

                foreach (var childParticle in this.ParticleConstraint.ChildrenParticles)
                {
                    Debug.Assert(childParticle is ElementParticle);

                    particleMatchInfo.ExpectedChildren.Add(childParticle.ElementId);
                }

                return;
            }
            else
            {
                particleMatchInfo.Match = ParticleMatch.Matched;

                // check if matched 
                foreach (var childParticle in this.ParticleConstraint.ChildrenParticles)
                {
                    Debug.Assert(childParticle is ElementParticle);
                    if (!this._childrenParticles[childParticle.ElementId] && childParticle.MinOccurs == 1)
                    {
                        // one of the required children are missed.
                        particleMatchInfo.Match = ParticleMatch.Partial;
                    }
                }

                // find expected child elements.
                foreach (var childParticle in this.ParticleConstraint.ChildrenParticles)
                {
                    if (!this._childrenParticles[childParticle.ElementId])
                    {
                        particleMatchInfo.ExpectedChildren.Add(childParticle.ElementId);
                    }
                }
                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;
            }
        }