/// <summary> /// Validate attributes on AlternateContent, Choice and Fallback element. /// </summary> /// <param name="validationContext"></param> /// <param name="acElement">The element to be validated.</param> private static void ValidateMcAttributesOnAcb(ValidationContext validationContext, OpenXmlElement acElement) { ValidationErrorInfo errorInfo; // AlternateContent elements might include the attributes Ignorable, MustUnderstand, ProcessContent, PreserveElements, and PreserveAttributes // These attributes’ qualified names shall be prefixed when associated with an AlternateContent / Choice / Fallback element. // A markup consumer shall generate an error if it encounters an unprefixed attribute name associated with an AlternateContent element. if (acElement.ExtendedAttributes != null) { foreach (var exAttribute in acElement.ExtendedAttributes) { if (string.IsNullOrEmpty(exAttribute.Prefix)) { // error on any unprefixed attributes errorInfo = validationContext.ComposeMcValidationError(acElement, ValidationResources.MC_ErrorOnUnprefixedAttributeName, exAttribute.XmlQualifiedName.ToString()); validationContext.AddError(errorInfo); } // Markup consumers shall generate an error if they encounter the xml:lang or xml:space attributes on an AlternateContent element. // Markup consumers shall generate an error if they encounter the xml:lang or xml:space attributes on a Choice element, regardless of whether the element is preceded by a selected Choice element. // Markup consumers shall generate an error if they encounter the xml:lang or xml:space attributes on a Fallback element, regardless of whether the element is preceded by a selected Choice element. if (IsXmlSpaceOrXmlLangAttribue(exAttribute)) { // report error. errorInfo = validationContext.ComposeMcValidationError(acElement, "MC_InvalidXmlAttribute", acElement.LocalName); validationContext.AddError(errorInfo); } } } // validate MC attribues (Ignorable, PreserveElements, etc.) of this element. CompatibilityRuleAttributesValidator.ValidateMcAttributes(validationContext); if (acElement is AlternateContentChoice choice) { // All Choice elements shall have a Requires attribute whose value contains a whitespace-delimited list of namespace prefixes if (choice.Requires == null) { // report error errorInfo = validationContext.ComposeMcValidationError(acElement, "MC_MissedRequiresAttribute"); validationContext.AddError(errorInfo); } else { var prefixes = new ListValue <StringValue>(); prefixes.InnerText = choice.Requires; foreach (var prefix in prefixes.Items) { var ignorableNamespace = choice.LookupNamespace(prefix); if (string.IsNullOrEmpty(ignorableNamespace)) { // report error, the prefix is not defined. errorInfo = validationContext.ComposeMcValidationError(choice, "MC_InvalidRequiresAttribute", choice.Requires); validationContext.AddError(errorInfo); } } } } }
/// <summary> /// Validate ACB syntax - AlternateContent, Choice, Fallback and their attributes. /// </summary> /// <param name="validationContext"></param> internal static void Validate(ValidationContext validationContext) { AlternateContent acElement = (AlternateContent)validationContext.Element; // Validate MC attribute on AlternateContent ValidateMcAttributesOnAcb(validationContext, acElement); int status = 0; ValidationErrorInfo errorInfo; if (acElement.ChildElements.Count == 0) { // Rule: An AlternateContent element shall contain one or more Choice child elements errorInfo = validationContext.ComposeMcValidationError(acElement, "Sch_IncompleteContentExpectingComplex", ValidationResources.MC_ShallContainChoice); validationContext.AddError(errorInfo); } OpenXmlElement child; child = acElement.GetFirstNonMiscElementChild(); while (child != null) { if (child is AlternateContent) { // Rule: An AlternateContent element shall not be the child of an AlternateContent element. errorInfo = validationContext.ComposeMcValidationError(acElement, "Sch_InvalidElementContentExpectingComplex", child.XmlQualifiedName.ToString(), ValidationResources.MC_ShallNotContainAlternateContent); validationContext.AddError(errorInfo); } else { switch (status) { case 0: // expect a Choice if (child is AlternateContentChoice) { // validate the MC attributes on Choice ValidateMcAttributesOnAcb(validationContext, child); status = 1; } else { // Rule: An AlternateContent element shall contain one or more Choice child elements errorInfo = validationContext.ComposeMcValidationError(acElement, "Sch_IncompleteContentExpectingComplex", ValidationResources.MC_ShallContainChoice); validationContext.AddError(errorInfo); if (child is AlternateContentFallback) { // validate the MC attributes on Fallback ValidateMcAttributesOnAcb(validationContext, child); } } break; case 1: // Already one Choice, expect Choice or Fallback if (child is AlternateContentChoice) { // validate the MC attributes on Choice ValidateMcAttributesOnAcb(validationContext, child); status = 1; } else if (child is AlternateContentFallback) { // validate the MC attributes on Fallback ValidateMcAttributesOnAcb(validationContext, child); status = 2; } else { errorInfo = validationContext.ComposeMcValidationError(acElement, "Sch_InvalidElementContentExpectingComplex", child.XmlQualifiedName.ToString(), ValidationResources.MC_ShallContainChoice); validationContext.AddError(errorInfo); } break; case 2: // Already one Fallback. Can not have more than one Fallback errorInfo = validationContext.ComposeMcValidationError(acElement, "Sch_InvalidElementContentExpectingComplex", child.XmlQualifiedName.ToString(), ValidationResources.MC_ShallContainChoice); validationContext.AddError(errorInfo); break; } } child = child.GetNextNonMiscElementSibling(); } return; }
/// <summary> /// Validate compatibility rule attributes - Ignorable, ProcessContent, PreserveElements, PreserveAttributes, MustUnderstand. /// </summary> /// <param name="validationContext">The validation context.</param> internal static void ValidateMcAttributes(ValidationContext validationContext) { var element = validationContext.Element; if (element.MCAttributes == null) { return; } HashSet <string> ignorableNamespaces = null; ValidationErrorInfo errorInfo; if (element.MCAttributes != null) { // validate Ignorable attribute if (!string.IsNullOrEmpty(element.MCAttributes.Ignorable)) { ignorableNamespaces = new HashSet <string>(); // rule: the prefix must already be defined. var prefixes = new ListValue <StringValue>(); prefixes.InnerText = element.MCAttributes.Ignorable; foreach (var prefix in prefixes.Items) { var ignorableNamespace = element.LookupNamespace(prefix); if (string.IsNullOrEmpty(ignorableNamespace)) { // error, the prefix is not defined. errorInfo = validationContext.ComposeMcValidationError(element, "MC_InvalidIgnorableAttribute", element.MCAttributes.Ignorable); validationContext.EmitError(errorInfo); } else { ignorableNamespaces.Add(ignorableNamespace); } } } // validate PreserveAttributes attribute if (!string.IsNullOrEmpty(element.MCAttributes.PreserveAttributes)) { // The ProcessAttributes attribute value shall not reference any attribute name that does not belong to a namespace // that is identified by the Ignorable attribute of the same element. if (ignorableNamespaces == null) { // must have Ignorable on same element. errorInfo = validationContext.ComposeMcValidationError(element, "MC_InvalidPreserveAttributesAttribute", element.MCAttributes.PreserveAttributes); validationContext.EmitError(errorInfo); } else { string errorQName = ValidateQNameList(element.MCAttributes.PreserveAttributes, ignorableNamespaces, validationContext); if (!string.IsNullOrEmpty(errorQName)) { errorInfo = validationContext.ComposeMcValidationError(element, "MC_InvalidPreserveAttributesAttribute", element.MCAttributes.PreserveAttributes); validationContext.EmitError(errorInfo); } } } // validate PreserveElements attribute if (!string.IsNullOrEmpty(element.MCAttributes.PreserveElements)) { // The ProcessAttributes attribute value shall not reference any attribute name that does not belong to a namespace // that is identified by the Ignorable attribute of the same element. if (ignorableNamespaces == null) { // must have Ignorable on same element. errorInfo = validationContext.ComposeMcValidationError(element, "MC_InvalidPreserveElementsAttribute", element.MCAttributes.PreserveElements); validationContext.EmitError(errorInfo); } else { string errorQName = ValidateQNameList(element.MCAttributes.PreserveElements, ignorableNamespaces, validationContext); if (!string.IsNullOrEmpty(errorQName)) { errorInfo = validationContext.ComposeMcValidationError(element, "MC_InvalidPreserveElementsAttribute", element.MCAttributes.PreserveElements); validationContext.EmitError(errorInfo); } } } // validate ProcessContent attribute if (!string.IsNullOrEmpty(element.MCAttributes.ProcessContent)) { // The ProcessAttributes attribute value shall not reference any attribute name that does not belong to a namespace // that is identified by the Ignorable attribute of the same element. if (ignorableNamespaces == null) { // must have Ignorable on same element. errorInfo = validationContext.ComposeMcValidationError(element, "MC_InvalidProcessContentAttribute", element.MCAttributes.ProcessContent); validationContext.EmitError(errorInfo); } else { string errorQName = ValidateQNameList(element.MCAttributes.ProcessContent, ignorableNamespaces, validationContext); if (!string.IsNullOrEmpty(errorQName)) { errorInfo = validationContext.ComposeMcValidationError(element, "MC_InvalidProcessContentAttribute", element.MCAttributes.ProcessContent); validationContext.EmitError(errorInfo); } } foreach (var exAttribute in element.ExtendedAttributes) { // Markup consumers that encounter a non-ignored element that has an xml:lang or xml:space attribute and is also identified by a ProcessContent attribute value might generate an error. if (AlternateContentValidator.IsXmlSpaceOrXmlLangAttribue(exAttribute)) { // report error. errorInfo = validationContext.ComposeMcValidationError(element, "MC_InvalidXmlAttributeWithProcessContent"); validationContext.EmitError(errorInfo); } } } if (!string.IsNullOrEmpty(element.MCAttributes.MustUnderstand)) { // TODO: MustUnderstand // A markup consumer that does not understand these identified namespaces shall not continue to process the markup document // rule: the prefix must already be defined. var prefixes = new ListValue <StringValue>(); prefixes.InnerText = element.MCAttributes.MustUnderstand; foreach (var prefix in prefixes.Items) { var mustunderstandNamespace = element.LookupNamespace(prefix); if (string.IsNullOrEmpty(mustunderstandNamespace)) { // report error, the prefix is not defined. errorInfo = validationContext.ComposeMcValidationError(element, "MC_InvalidMustUnderstandAttribute", element.MCAttributes.MustUnderstand); validationContext.EmitError(errorInfo); } } } } }