/// <summary>
        /// Validates a template against the schema to determine if any constraints within the template cause don't align with the schema.
        /// </summary>
        /// <param name="errors">The errors list that should be populated when issues occur matching the template/constraints to the schema.</param>
        /// <param name="templateSchema">The schema that the template should be validated against.</param>
        public static List <TemplateValidationResult> ValidateTemplate(this Template template, SimpleSchema templateSchema = null, SimpleSchema igSchema = null)
        {
            if (templateSchema == null)
            {
                templateSchema = template.GetSchema(igSchema);
            }

            List <TemplateValidationResult> errors = new List <TemplateValidationResult>();

            XmlDocument    tempDoc        = new XmlDocument();
            XPathNavigator xpathNavigator = tempDoc.CreateNavigator();

            if (templateSchema == null)
            {
                errors.Add(
                    TemplateValidationResult.CreateResult(ValidationLevels.Error, "Template context is not found within the schema"));
            }
            else
            {
                List <TemplateConstraint> rootConstraints = template.ChildConstraints.Where(y => y.ParentConstraintId == null).ToList();
                rootConstraints.ForEach(y =>
                {
                    // The first child in the schema is the complex type itself
                    template.ValidateTemplateConstraint(xpathNavigator, errors, templateSchema, templateSchema.Children, y);
                });
            }

            return(errors);
        }
Beispiel #2
0
        protected virtual ValidationResults ValidateImplementationGuide(ImplementationGuide implementationGuide, SimpleSchema igSchema)
        {
            ValidationResults results = new ValidationResults();

            var plugin               = implementationGuide.ImplementationGuideType.GetPlugin();
            var childTemplateIds     = implementationGuide.ChildTemplates.Select(y => y.Id);
            var containedTemplateIds = (from vtr in this.tdb.ViewTemplateRelationships.AsNoTracking()
                                        join pt in childTemplateIds on vtr.ParentTemplateId equals pt
                                        join ct in this.tdb.Templates.AsNoTracking() on vtr.ChildTemplateId equals ct.Id
                                        select ct.Id).Distinct();
            var containedTemplates = (from cti in containedTemplateIds
                                      join t in this.tdb.Templates.AsNoTracking() on cti equals t.Id
                                      select t).ToList();
            var valueSets = (from tc in this.tdb.TemplateConstraints
                             join t in this.tdb.Templates on tc.TemplateId equals t.Id
                             join vs in this.tdb.ValueSets on tc.ValueSetId equals vs.Id
                             join cti in childTemplateIds on t.Id equals cti
                             select vs);
            var valueSetCodesWithWhitespace = (from vsm in this.tdb.ViewValueSetMemberWhiteSpaces
                                               join vs in valueSets on vsm.ValueSetId equals vs.Id
                                               select new
            {
                ValueSet = vs,
                ValueSetMember = vsm
            });

            foreach (var section in implementationGuide.Sections)
            {
                try
                {
                    string      html = section.Content.MarkdownToHtml();
                    XmlDocument doc  = new XmlDocument();
                    doc.LoadXml("<root>" + html + "</root>");
                }
                catch (Exception ex)
                {
                    results.Messages.Add(string.Format("Section \"{0}\" includes invalid HTML. This section will not output correctly in exports.", section.Heading));
                    Logging.Log.For(this).Error("Implementation guide {0} has invalid HTML for section {1} (id {2})\n{3}\n\n{4}\n", implementationGuide.Id, section.Heading, section.Id, ex.Message, section.Content.MarkdownToHtml());
                }

                if (section.Level < 1 || section.Level > 9)
                {
                    results.Messages.Add(string.Format("Section \"{0}\" has an invalid heading level. Only levels 1-9 are supported.", section.Heading));
                }
            }

            foreach (var template in implementationGuide.ChildTemplates)
            {
                var result = new TemplateValidationResult()
                {
                    Id    = template.Id,
                    Name  = template.Name,
                    Oid   = template.Oid,
                    Items = this.ValidateTemplate(template, igSchema, containedTemplates)
                };

                if (result.Items.Count > 0)
                {
                    results.TemplateResults.Add(result);
                }
            }

            foreach (var vscww in valueSetCodesWithWhitespace)
            {
                var identifier = vscww.ValueSet.GetIdentifier(plugin);

                if (vscww.ValueSetMember.Code.Trim() != vscww.ValueSetMember.Code)
                {
                    results.Messages.Add("Value set \"" + vscww.ValueSet.Name + "\" (" + identifier + ") has a code \"" + vscww.ValueSetMember.Code + "\" with leading or trailing white spaces.");
                }

                if (vscww.ValueSetMember.DisplayName.Trim() != vscww.ValueSetMember.DisplayName)
                {
                    results.Messages.Add("Value set \"" + vscww.ValueSet.Name + "\" (" + identifier + ") has a code \"" + vscww.ValueSetMember.Code + "\" whose display name \"" + vscww.ValueSetMember.DisplayName + "\" has leading or trailing white spaces.");
                }
            }

            if (implementationGuide.HasImportedValueSets(this.tdb, ValueSetImportSources.VSAC))
            {
                User currentUser = CheckPoint.Instance.GetUser(this.tdb);

                try
                {
                    if (!currentUser.HasValidUmlsLicense())
                    {
                        results.RestrictDownload = true;
                        results.Messages.Add("This implementation guide contains VSAC content that you do not currently have a license to. <a href=\"/Account/MyProfile\">Update your profile</a> with your UMLS/VSAC credentials to export this implementation guide.");
                    }
                }
                catch (Exception ex)
                {
                    results.RestrictDownload = true;
                    results.Messages.Add("This implementation guide contains VSAC content. Your UMLS license could not be verified due to an error. Please submit a support request related to this message.");
                    Logging.Log.For(this).Error("Error checking if the user {0} has a valid UMLS license", ex, currentUser.UserName);
                    throw ex;
                }
            }

            return(results);
        }
        /// <summary>
        /// Perform validations on a single constraint.
        /// </summary>
        /// <param name="errors">The list of errors that should be added to when a constraint has an error matching the schema</param>
        /// <param name="schemaObjects">The list of sibling-level schema objects that the constraint should be compared to</param>
        /// <param name="currentConstraint">The current constraint to match against the schema</param>
        public static void ValidateTemplateConstraint(this Template template, XPathNavigator xpathNavigator, List <TemplateValidationResult> errors, SimpleSchema schema, List <SimpleSchema.SchemaObject> schemaObjects, TemplateConstraint currentConstraint)
        {
            List <TemplateConstraint> childConstraints = currentConstraint.ChildConstraints.ToList();

            if (!string.IsNullOrEmpty(currentConstraint.Schematron))
            {
                try
                {
                    XPathExpression expr = xpathNavigator.Compile(currentConstraint.Schematron);
                }
                catch (XPathException ex)
                {
                    errors.Add(TemplateValidationResult.CreateResult(currentConstraint.Number, ValidationLevels.Error, "Custom schematron is not valid: " + ex.Message));
                }
            }

            if (currentConstraint.IsPrimitive && string.IsNullOrEmpty(currentConstraint.PrimitiveText))
            {
                errors.Add(TemplateValidationResult.CreateResult(currentConstraint.Number, ValidationLevels.Error, "Primitive does not have any narrative text."));
                return;
            }
            else if (!string.IsNullOrEmpty(currentConstraint.Context))
            {
                string context     = currentConstraint.Context;
                bool   isAttribute = context.StartsWith("@");

                // If it is an attribute, then we need to remove the @ from the context
                if (isAttribute)
                {
                    context = context.Substring(1);
                }

                SimpleSchema.SchemaObject foundSchemaObject = schemaObjects.SingleOrDefault(y => y.Name.ToLower() == context.ToLower() && y.IsAttribute == isAttribute);

                // Verify that the template (if specified) matches the datatype of the constraints
                if (currentConstraint.ContainedTemplate != null &&
                    currentConstraint.ContainedTemplate.PrimaryContextType != null &&
                    foundSchemaObject != null)
                {
                    string containedTemplateDataType = currentConstraint.ContainedTemplate.PrimaryContextType;
                    string constraintDataType        = !string.IsNullOrEmpty(currentConstraint.DataType) ? currentConstraint.DataType : foundSchemaObject.DataType;

                    bool isFhirResourceReference = schema.Schema.TargetNamespace == "http://hl7.org/fhir" && (constraintDataType == "ResourceReference" || constraintDataType == "Reference");

                    if (!isFhirResourceReference && containedTemplateDataType.ToLower() != constraintDataType.ToLower())
                    {
                        errors.Add(TemplateValidationResult.CreateResult(
                                       currentConstraint.Number,
                                       ValidationLevels.Error,
                                       "Contained template \"{0}\" has a type of \"{1}\" which does not match the containing element \"{2}\"",
                                       currentConstraint.ContainedTemplate.Oid,
                                       containedTemplateDataType,
                                       constraintDataType));
                    }
                }

                // Verify that branched elements have at least one identifier
                if (currentConstraint.IsBranch && childConstraints.Count(y => y.IsBranchIdentifier) == 0)
                {
                    errors.Add(TemplateValidationResult.CreateResult(currentConstraint.Number, ValidationLevels.Warning, "Branched constraint \"{0}\" does not have any identifiers associated with it.", currentConstraint.GetXpath()));
                }

                // Verify that a schema-object can be matched to this constraint
                if (foundSchemaObject == null)
                {
                    errors.Add(TemplateValidationResult.CreateResult(currentConstraint.Number, ValidationLevels.Error, "Constraint has context of \"{0}\" which is not found in the schema.", currentConstraint.GetXpath()));
                    return; // Do not process child constraints when the parent is not matched to the schema
                }
                else if (foundSchemaObject.Cardinality != null && currentConstraint.Cardinality != null)
                {
                    // Warn when a constraint that is associated with the schema that has multiple cardinality but is not branched
                    if (template.IsOpen &&
                        !foundSchemaObject.Cardinality.EndsWith("..1") &&
                        !foundSchemaObject.Cardinality.EndsWith("..0") &&
                        !currentConstraint.IsBranch)
                    {
                        var error = TemplateValidationResult.CreateResult(currentConstraint.Number, ValidationLevels.Warning, "Schema allows multiple for \"{0}\" but the constraint is not branched. Consider branching this constraint.", currentConstraint.GetXpath());
                        errors.Add(error);
                    }

                    if (foundSchemaObject.Cardinality.EndsWith("..1") && !(currentConstraint.Cardinality.EndsWith("..0") || currentConstraint.Cardinality.EndsWith("..1")))
                    {
                        var error = TemplateValidationResult.CreateResult(currentConstraint.Number, ValidationLevels.Error, "The cardinality for the element is loosened in the constraint from the underlying schema.");
                        errors.Add(error);
                    }
                }

                List <SimpleSchema.SchemaObject> childSchemaObjects = foundSchemaObject.Children;

                // If a data-type is specified, then we should find the data-type within the schema, since it is likely different than what
                // is specified by default.
                if (!string.IsNullOrEmpty(currentConstraint.DataType))
                {
                    SimpleSchema.SchemaObject foundSchemaTypeObject = schema.FindFromType(currentConstraint.DataType);

                    if (foundSchemaTypeObject != null)
                    {
                        childSchemaObjects = foundSchemaTypeObject.Children;
                    }
                }

                childConstraints.ForEach(y => template.ValidateTemplateConstraint(xpathNavigator, errors, schema, childSchemaObjects, y));
            }
        }