/// <summary> /// Try match this element. /// </summary> /// <param name="particleMatchInfo"></param> /// <param name="validationContext">The context information for validation.</param> public void TryMatch(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext) { Debug.Assert(particleMatchInfo != null); Debug.Assert(particleMatchInfo.StartElement != null); Debug.Assert(!(particleMatchInfo.StartElement is OpenXmlMiscNode)); if (this.ParticleConstraint.MaxOccurs == 1) { this.TryMatchOnce(particleMatchInfo, validationContext); } else { // try to match multiple times. ParticleMatchInfo nextParticleMatchInfo; int matchCount = 0; var next = particleMatchInfo.StartElement; while (next != null && this.ParticleConstraint.MaxOccursGreaterThan(matchCount)) { nextParticleMatchInfo = new ParticleMatchInfo(next); this.TryMatchOnce(nextParticleMatchInfo, validationContext); if (nextParticleMatchInfo.Match == ParticleMatch.Nomatch) { break; } else if (nextParticleMatchInfo.Match == ParticleMatch.Matched) { matchCount++; particleMatchInfo.LastMatchedElement = nextParticleMatchInfo.LastMatchedElement; next = validationContext.GetNextChildMc(particleMatchInfo.LastMatchedElement); } else { // never go here Debug.Assert( nextParticleMatchInfo.Match != ParticleMatch.Partial ); } } if (matchCount == 0) { particleMatchInfo.Match = ParticleMatch.Nomatch; } else if (matchCount >= this.ParticleConstraint.MinOccurs) { // matched ok particleMatchInfo.Match = ParticleMatch.Matched; } else { // minOccurs failed, incomplete children. particleMatchInfo.Match = ParticleMatch.Partial; } } 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> 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 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 override void EmitInvalidElementError(ValidationContext validationContext, ParticleMatchInfo particleMatchInfo) { var element = validationContext.Element; OpenXmlElement child; if (particleMatchInfo.LastMatchedElement == null) { child = validationContext.GetFirstChildMc(); } else { child = validationContext.GetNextChildMc(particleMatchInfo.LastMatchedElement); } string expectedChildren; ValidationErrorInfo errorInfo; switch (particleMatchInfo.Match) { case ParticleMatch.Nomatch: expectedChildren = GetExpectedChildrenMessage(validationContext.Element, this.GetExpectedElements()); errorInfo = validationContext.ComposeSchemaValidationError(element, child, "Sch_InvalidElementContentExpectingComplex", child.XmlQualifiedName.ToString(), expectedChildren); validationContext.EmitError(errorInfo); break; case ParticleMatch.Partial: case ParticleMatch.Matched: if (this._childrenParticles.ContainsKey(child.ElementTypeId)) { // more than one occurs of a child. errorInfo = validationContext.ComposeSchemaValidationError(element, child, "Sch_AllElement", child.XmlQualifiedName.ToString()); validationContext.EmitError(errorInfo); } else { expectedChildren = GetExpectedChildrenMessage(validationContext.Element, particleMatchInfo.ExpectedChildren); errorInfo = validationContext.ComposeSchemaValidationError(element, child, "Sch_InvalidElementContentExpectingComplex", child.XmlQualifiedName.ToString(), expectedChildren); validationContext.EmitError(errorInfo); } break; } }
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; }
/// <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; }