/// <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 = ParticleConstraint.ChildrenParticles.Length; var childMatchInfo = new ParticleMatchInfo(); while (constraintIndex < constraintTotal && next != null) { childConstraint = ParticleConstraint.ChildrenParticles[constraintIndex]; // Use Reset() instead of new() to avoid heavy memory allocation 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; }
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(); TryMatch(particleMatchInfo, validationContext); validationContext.CollectExpectedChildren = false; #if DEBUG Debug.Assert(particleMatchInfo.Match == oldParticleMatchInfo.Match); Debug.Assert(particleMatchInfo.LastMatchedElement == oldParticleMatchInfo.LastMatchedElement); #endif } var element = validationContext.Stack.Current.Element; if (particleMatchInfo.LastMatchedElement is null) { child = validationContext.GetFirstChildMc(); } else { child = validationContext.GetNextChildMc(particleMatchInfo.LastMatchedElement); } ValidationErrorInfo errorInfo; string expectedChildren = null; switch (particleMatchInfo.Match) { case ParticleMatch.Nomatch: expectedChildren = GetExpectedChildrenMessage(validationContext.Stack.Current.Element, GetExpectedElements()); break; case ParticleMatch.Partial: // error: the child can not be matched, it is invalid if (child is null) { // missing child errorInfo = validationContext.ComposeSchemaValidationError(element, null, "Sch_IncompleteContentExpectingComplex", GetExpectedChildrenMessage(element, particleMatchInfo.ExpectedChildren)); validationContext.AddError(errorInfo); return; } else { expectedChildren = GetExpectedChildrenMessage(validationContext.Stack.Current.Element, particleMatchInfo.ExpectedChildren); } break; case ParticleMatch.Matched: if (ParticleConstraint.CanOccursMoreThanOne) { expectedChildren = GetExpectedChildrenMessage(validationContext.Stack.Current.Element, GetExpectedElements()); } else { expectedChildren = GetExpectedChildrenMessage(validationContext.Stack.Current.Element, particleMatchInfo.ExpectedChildren); } break; } if (validationContext.Stack.Current.Element.CanContainChild(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 { // Same element name, but wrong type. Only occurs when validating memory DOM. var validElement = element.TryCreateValidChild(validationContext.FileFormat, child.NamespaceUri, child.LocalName); if (validElement is 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.AddError(errorInfo); }
public override void TryMatch(ParticleMatchInfo particleMatchInfo, ValidationContext validationContext) { if (ParticleConstraint.MaxOccurs == 1) { TryMatchOnce(particleMatchInfo, validationContext); } else { int matchCount = 0; var next = particleMatchInfo.StartElement; var childMatchInfo = new ParticleMatchInfo(); while (next is not null && ParticleConstraint.MaxOccursGreaterThan(matchCount)) { // Use Reset() instead of new() to avoid heavy memory allocation and GC. childMatchInfo.Reset(next); 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(GetExpectedElements()); } } else if (matchCount >= ParticleConstraint.MinOccurs) { // matched OK particleMatchInfo.Match = ParticleMatch.Matched; } else { if (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> /// 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; }