/// <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);
        }
        /// <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));
            }
        }