private void AddTemplateTableConstraint(Template template, Table table, TemplateConstraint constraint, int level, bool includeCategoryHeader, SimpleSchema.SchemaObject schemaObject) { if (constraint.IsPrimitive != true && !string.IsNullOrEmpty(constraint.Context)) { string xpath = constraint.Context; string cardinality = constraint.Cardinality; string conformance = constraint.Conformance; string dataType = constraint.DataType; string fixedValue = string.Empty; string fixedValueLink = string.Empty; string levelSpacing = string.Empty; string confNumber = constraint.GetFormattedNumber(this.igSettings.PublishDate); var isFhir = constraint.Template.ImplementationGuideType.SchemaURI == ImplementationGuideType.FHIR_NS; // Check if we're dealing with a FHIR constraint if (isFhir && schemaObject != null) { dataType = schemaObject.DataType; } if (constraint.ValueSet != null) { fixedValue = string.Format("{0} ({1})", constraint.ValueSet.Oid, constraint.ValueSet.Name); } else if (constraint.CodeSystem != null) { fixedValue = string.Format("{0} ({1})", constraint.CodeSystem.Oid, constraint.CodeSystem.Name); } if (!string.IsNullOrEmpty(constraint.Value)) { if (!string.IsNullOrEmpty(fixedValue)) { fixedValue += " = " + constraint.Value; } else { fixedValue = constraint.Value; } } else if (constraint.ContainedTemplate != null) { fixedValue = string.Format("{0} (identifier: {1}", constraint.ContainedTemplate.Name, constraint.ContainedTemplate.Oid); if (this.templates.Contains(constraint.ContainedTemplate)) { fixedValueLink = constraint.ContainedTemplate.Bookmark; } } for (int i = 1; i <= (level); i++) // One tab for each level { levelSpacing += "\t"; } TableRow entryRow = new TableRow(); if (includeCategoryHeader) { AppendTextCell(entryRow, constraint.Category); } AppendTextCell(entryRow, levelSpacing + xpath); AppendTextCell(entryRow, cardinality); AppendTextCell(entryRow, conformance); AppendTextCell(entryRow, dataType); AppendHyperlinkCell(entryRow, confNumber, "C_" + confNumber); if (!string.IsNullOrEmpty(fixedValueLink)) { AppendHyperlinkCell(entryRow, fixedValue, fixedValueLink); } else { AppendTextCell(entryRow, fixedValue); } table.AppendChild(entryRow); } // Recursively handle child constraints var childConstraints = template.ChildConstraints .Where(y => y.ParentConstraintId == constraint.Id) .OrderBy(y => y.Order); foreach (TemplateConstraint cConstraint in childConstraints) { if (this.HasSelectedCategories && !string.IsNullOrEmpty(cConstraint.Category) && !this.selectedCategories.Contains(cConstraint.Category)) { continue; } var nextSchemaObject = schemaObject != null? schemaObject.Children.SingleOrDefault(y => y.Name == cConstraint.Context) : null; this.AddTemplateTableConstraint(template, table, cConstraint, level + 1, includeCategoryHeader, nextSchemaObject); } }
/// <summary> /// Perform validations on a single constraint. /// </summary> /// <param name="results">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 void ValidateTemplateConstraint( Template template, XPathNavigator xpathNavigator, List <ValidationResult> results, SimpleSchema schema, List <SimpleSchema.SchemaObject> schemaObjects, TemplateConstraint currentConstraint, IEnumerable <Template> allContainedTemplates) { List <TemplateConstraint> childConstraints = currentConstraint.ChildConstraints.ToList(); if (!string.IsNullOrEmpty(currentConstraint.Schematron)) { try { XPathExpression expr = xpathNavigator.Compile(currentConstraint.Schematron); } catch (XPathException ex) { results.Add(ValidationResult.CreateResult(template.Id, template.Name, currentConstraint.Number.Value, ValidationLevels.Error, "Custom schematron is not valid: " + ex.Message)); } } if (currentConstraint.IsPrimitive && string.IsNullOrEmpty(currentConstraint.PrimitiveText)) { results.Add(ValidationResult.CreateResult(template.Id, template.Name, currentConstraint.Number.Value, 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 (foundSchemaObject != null) { var templates = allContainedTemplates != null ? allContainedTemplates : this.tdb.Templates.AsEnumerable(); var containedTemplates = (from tcr in currentConstraint.References join t in templates on tcr.ReferenceIdentifier equals t.Oid where tcr.ReferenceType == ConstraintReferenceTypes.Template select t); string constraintDataType = !string.IsNullOrEmpty(currentConstraint.DataType) ? currentConstraint.DataType : foundSchemaObject.DataType; foreach (var containedTemplate in containedTemplates) { string containedTemplateDataType = containedTemplate.PrimaryContextType; bool isFhirResourceReference = schema.Schema.TargetNamespace == "http://hl7.org/fhir" && (constraintDataType == "ResourceReference" || constraintDataType == "Reference"); if (!isFhirResourceReference && containedTemplateDataType.ToLower() != constraintDataType.ToLower()) { results.Add(ValidationResult.CreateResult( template.Id, template.Name, currentConstraint.Number.Value, ValidationLevels.Error, "Contained template \"{0}\" has a type of \"{1}\" which does not match the containing element \"{2}\"", containedTemplate.Oid, containedTemplateDataType, constraintDataType)); } } } // Verify that branched elements have at least one identifier if (currentConstraint.IsBranch && childConstraints.Count(y => y.IsBranchIdentifier) == 0) { results.Add(ValidationResult.CreateResult(template.Id, template.Name, currentConstraint.Number.Value, 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) { results.Add(ValidationResult.CreateResult(template.Id, template.Name, currentConstraint.Number.Value, 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) { var siblings = currentConstraint.ParentConstraint != null ? currentConstraint.ParentConstraint.ChildConstraints : template.ChildConstraints.Where(y => y.ParentConstraint == null); var isDuplicate = siblings.Count(y => y.Context == currentConstraint.Context) > 1; // 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 && isDuplicate) { var error = ValidationResult.CreateResult(template.Id, template.Name, currentConstraint.Number.Value, ValidationLevels.Warning, "Schema allows multiple for \"{0}\" ({1}) but the constraint is not branched. Consider branching this constraint.", currentConstraint.GetXpath(), currentConstraint.GetFormattedNumber()); results.Add(error); } if (foundSchemaObject.Cardinality.EndsWith("..1") && !(currentConstraint.Cardinality.EndsWith("..0") || currentConstraint.Cardinality.EndsWith("..1"))) { var error = ValidationResult.CreateResult(template.Id, template.Name, currentConstraint.Number.Value, ValidationLevels.Error, "The cardinality for the element is loosened in the constraint from the underlying schema."); results.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 => ValidateTemplateConstraint(template, xpathNavigator, results, schema, childSchemaObjects, y, allContainedTemplates)); } }