/// <summary> /// Takes a given child element (aChildElement) and parent constraint (aParentTemplateConstraint) and steps through the Children of the parent constraint /// to determine if any siblings of the child element exist. /// </summary> /// <param name="aChildElement">Target element that we need to locate siblings for</param> /// <param name="aParentTemplateConstraint">Parent Constraint of the aChildElement</param> /// <param name="aParentElement">Parent Element for the aChildElement</param> /// <param name="aAddedConstraints">Constraints that have already been added to the child collection, we don't need to parse these.</param> static private void AddSiblingElements(DocumentTemplateElement aChildElement, IConstraint aParentTemplateConstraint, DocumentTemplateElement aParentElement, Dictionary <DocumentTemplateElement, IConstraint> aConstraintMap) { //look at parent to get the siblings if (aParentTemplateConstraint.Children != null) { DocumentTemplateElement parsedElement = null; DocumentTemplateElementAttribute parsedAttribute = null; //walk through the siblings foreach (var sibling in aParentTemplateConstraint.Children) { //have we already added this constraint in a previous iteration (e.g. it's on the main path from the leaf to the root) if (sibling.IsBranchIdentifier && !aConstraintMap.ContainsValue(sibling)) { //parse the context var cp = new ContextParser(sibling.Context); cp.Parse(out parsedElement, out parsedAttribute); //is this an element or an attribute? if ((parsedElement != null) && (!string.IsNullOrEmpty(parsedElement.ElementName))) { parsedElement.IsBranchIdentifier = sibling.IsBranchIdentifier; parsedElement.IsBranch = sibling.IsBranch; //element, let's add it to the parent element's children so it becomes a sibling of aChildElement parsedElement.Value = sibling.Value; aParentElement.AddElement(parsedElement); AddBranchedAttributes(parsedElement, sibling); aConstraintMap.Add(parsedElement, sibling); } } } } }
static public DocumentTemplateElement CreateParentElementForAttribute(IConstraint aConstraint, DocumentTemplateElementAttribute aAttribute) { DocumentTemplateElement parentElement = null; // Is there a parent constraint that should be used as a context if (aConstraint.Parent != null && !string.IsNullOrEmpty(aConstraint.Parent.Context) && !aConstraint.Parent.IsBranch) { if ((aAttribute != null) && (aAttribute.Element == null)) //we have an attribute, but no element attached. the parent constraint would then be the element. { parentElement = new DocumentTemplateElement(aConstraint.Parent.Context); if (aConstraint.Parent.IsBranch) { parentElement.AddAttribute(aAttribute); } } else if ((aAttribute != null) && (aAttribute.Element != null) && (aConstraint.Parent.Context != aAttribute.Element.ElementName)) //we have an attribute, with an element attached, but the element does not match the parent context { parentElement = new DocumentTemplateElement(aConstraint.Parent.Context); parentElement.AddElement(aAttribute.Element); } else if (aAttribute != null && aAttribute.Element != null) { parentElement = aAttribute.Element; } } return(parentElement); }
/// <summary> /// Uses back-tracing algorithm to go backwards through the tree /// Helper function which builds the full parent context for a given template constraint. For example, for the template constraint @code with cda:entryRelationship/cda:observation/cda:code[@code] /// this function returns the cda:entryRelationship/cda:observation/cda:code. /// </summary> /// <param name="aElement">current element to start from</param> /// <param name="aTemplateConstraint">constraint which will have its parent chain walked to form path</param> /// <param name="aIncludeElementInPath">determines whether we start the path with the element passed in (true) or its parent (false)</param> /// <returns>full context string</returns> public static string CreateFullParentContext(string aPrefix, IConstraint aTemplateConstraint) { if (aTemplateConstraint == null) { return(string.Empty); } DocumentTemplateElement firstElement = null; DocumentTemplateElement newElement = null; DocumentTemplateElement previousElement = null; DocumentTemplateElementAttribute newAttribute = null; IConstraint currentConstraint = aTemplateConstraint.Parent; while (currentConstraint != null) { //parse the context to determine whether this is element or attribute var contextParser = new ContextParser(currentConstraint.Context); contextParser.Parse(out newElement, out newAttribute); newElement.Attributes.Clear(); if (currentConstraint.IsBranch) //if we hit a branch then we stop b/c we are in the branch's context { break; } if (newElement == null) { break; //there is a broken chain, we have null parent } //add value and data type (if present) ConstraintToDocumentElementHelper.AddElementValueAndDataType(aPrefix, newElement, currentConstraint); //chain the previous element to the child collection of this new one if (previousElement != null) { newElement.AddElement(previousElement); } //get the leaf node if (firstElement == null) { firstElement = newElement; } previousElement = newElement; //walk the parent chain currentConstraint = currentConstraint.Parent; } if (firstElement == null) { return(string.Empty); } else { var contextBuilder = new ContextBuilder(firstElement, aPrefix); return(contextBuilder.GetFullyQualifiedContextString()); } }
public void TestCDADocumentTemplate_WithValidNamespace_SingleChildElement_SingleChildElement_ChildCountIsOne() { var cdaDocumentTemplate = new DocumentTemplate("urn:hl7-org:v3"); var element = new DocumentTemplateElement("author"); Assert.IsNotNull(element.ChildElements, "element.ChildElements is null, expected instance"); element.AddElement(new DocumentTemplateElement("assignedPerson")); cdaDocumentTemplate.AddElement(element); Assert.IsNotNull(cdaDocumentTemplate.ChildElements, "cdaDocumentTemplate.RootElements is null, expected instance."); Assert.IsTrue(cdaDocumentTemplate.ChildElements.Count == 1, "Root element count failed, expected 1, actual {0}", cdaDocumentTemplate.ChildElements.Count); Assert.IsTrue(cdaDocumentTemplate.ChildElements[0].ChildElements.Count == 1, "Child element count failed, expected 1, actual {0}", cdaDocumentTemplate.ChildElements[0].ChildElements.Count); Assert.AreEqual(cdaDocumentTemplate.ChildElements[0].ChildElements[0].ParentElement, cdaDocumentTemplate.ChildElements[0], "Child element parent was not set properly."); }
public void GenerateContext_SingleChildElement_4Levels_GenerateContextOn3rdLevel() { var administrativeCodeElement = new DocumentTemplateElement("administrativeGenderCode"); administrativeCodeElement.AddAttribute(new DocumentTemplateElementAttribute("code", "20", string.Empty, "MMG-GENDER-CODE-OID")); var patientCodeElement = new DocumentTemplateElement("patient"); var cdaDocumentTemplate = new DocumentTemplate("urn:hl7-org:v3"); cdaDocumentTemplate.AddElement(new DocumentTemplateElement("recordTarget") .AddElement(new DocumentTemplateElement("patientRole") .AddElement(patientCodeElement .AddElement(administrativeCodeElement)))); var contextBuilder = new ContextBuilder(patientCodeElement, "cda"); var context = contextBuilder.GetFullyQualifiedContextString(); var expected = "cda:recordTarget/cda:patientRole/cda:patient"; Assert.IsFalse(string.IsNullOrEmpty(context), "Null or empty string returned by context builder"); Assert.IsTrue(context == expected, "Context string was not correct, expected '{0}', actual '{1}'", expected, context); }
public void Parse(out DocumentTemplateElement aContextElement, out DocumentTemplateElementAttribute aAttribute) { aContextElement = null; //default aAttribute = null; //default var parsedContext = _context.Split('/'); if (parsedContext.Length > 1) //does the context contain a complex element structure (e.g. code/@code) { DocumentTemplateElement parentContextElement = null; for (int i = 0; i < parsedContext.Length; i++) { if (IsAttribute(parsedContext[i])) { aAttribute = new DocumentTemplateElementAttribute(parsedContext[i].Replace("@", "")); aContextElement.AddAttribute(aAttribute); } else { aContextElement = new DocumentTemplateElement(parsedContext[i]); if (parentContextElement != null) { parentContextElement.AddElement(aContextElement); } } parentContextElement = aContextElement; } } else { if (IsAttribute(_context)) { aAttribute = new DocumentTemplateElementAttribute(_context.Replace("@", "")); } else { aContextElement = new DocumentTemplateElement(_context); } } }
public void BuildAdvanceDirectiveObservationDocument_1stLevelOnly() { var sectionCount = 1; var phase = new Phase(); phase.ID = "error"; var document = new SchematronDocument(); document.Phases.Add(phase); var doc = new DocumentTemplate("cda"); doc.AddElement(new DocumentTemplateElement("observation")); doc.ChildElements[0].AddAttribute(new DocumentTemplateElementAttribute("classCode", "OBS")); doc.ChildElements[0].AddAttribute(new DocumentTemplateElementAttribute("moodCode", "EVN")); doc.AddElement(new DocumentTemplateElement("templateId")); doc.ChildElements[1].AddAttribute(new DocumentTemplateElementAttribute("root", "2.16.840.1.113883.10.20.22.4.48")); doc.AddElement(new DocumentTemplateElement("id")); doc.AddElement(new DocumentTemplateElement("code")); doc.ChildElements[doc.ChildElements.Count - 1].AddAttribute(new DocumentTemplateElementAttribute("xsi-type", "CE", "2.16.840.1.113883.1.11.20.2")); doc.AddElement(new DocumentTemplateElement("statusCode")); doc.ChildElements[doc.ChildElements.Count - 1].AddAttribute(new DocumentTemplateElementAttribute("code", "completed", "2.16.840.1.113883.5.14")); var participantElement = new DocumentTemplateElement("participant"); doc.ChildElements[0].AddElement(participantElement); participantElement.AddAttribute(new DocumentTemplateElementAttribute("typeCode", "VRF")); var templateIdElement = new DocumentTemplateElement("templateId"); templateIdElement.AddAttribute(new DocumentTemplateElementAttribute("root", "2.16.840.1.113883.10.20.1.58")); participantElement.AddElement(templateIdElement); var timeElement = new DocumentTemplateElement("time"); timeElement.AddAttribute(new DocumentTemplateElementAttribute("xsi:type", "TS")); participantElement.AddElement(timeElement); var participantRoleElement = new DocumentTemplateElement("participantRole"); participantElement.AddElement(participantRoleElement); var contextBuilder = new ContextBuilder(doc.ChildElements[0], "cda"); var rule = new Rule(); rule.Context = contextBuilder.GetFullyQualifiedContextString(); var assertionBuilder = new AssertionLineBuilder(doc.ChildElements[0].Attributes[0], templateIdentifierXpath, templateVersionIdentifierXpath); //"OBS" rule.Assertions.Add(new Assertion() { AssertionMessage = "SHALL contain 1..1 @classCode='OBS' Observation (CodeSystem: HL7ActClass 2.16.840.1.113883.5.6) (CONF:8648).", Test = assertionBuilder.WithCardinality(CardinalityParser.Parse("1..1")).WithinContext(contextBuilder.GetRelativeContextString()).ConformsTo(Conformance.SHALL).ToString() }); assertionBuilder = new AssertionLineBuilder(doc.ChildElements[0].Attributes[1], templateIdentifierXpath, templateVersionIdentifierXpath); //"EVN" rule.Assertions.Add(new Assertion() { AssertionMessage = "SHALL contain 1..1 @moodCode='EVN' Event (CodeSystem: ActMood 2.16.840.1.113883.5.1001) (CONF:8649).", Test = assertionBuilder.WithCardinality(CardinalityParser.Parse("1..1")).WithinContext(contextBuilder.GetRelativeContextString()).ConformsTo(Conformance.SHALL).ToString() }); var pattern = new Pattern(); pattern.ID = sectionCount.ToString(); pattern.Name = string.Format("pattern-{0}-errors", pattern.ID); pattern.Rules.Add(rule); phase.ActivePatterns.Add(pattern); rule = new Rule(); contextBuilder = new ContextBuilder(doc.ChildElements[1], "cda"); rule.Context = contextBuilder.GetFullyQualifiedContextString(); assertionBuilder = new AssertionLineBuilder(doc.ChildElements[1], templateIdentifierXpath, templateVersionIdentifierXpath); //"templateId[@rootCode]" rule.Assertions.Add(new Assertion() { AssertionMessage = "SHALL contain 1..1 @root='2.16.840.1.113883.10.20.22.4.48' (CONF:10485).", Test = assertionBuilder.WithCardinality(CardinalityParser.Parse("1..1")).WithinContext(contextBuilder.GetRelativeContextString()).ConformsTo(Conformance.SHALL).ToString() }); sectionCount++; pattern = new Pattern(); pattern.ID = sectionCount.ToString(); pattern.Name = string.Format("pattern-{0}-errors", pattern.ID); pattern.Rules.Add(rule); phase.ActivePatterns.Add(pattern); rule = new Rule(); contextBuilder = new ContextBuilder(doc.ChildElements[2], "cda"); rule.Context = contextBuilder.GetFullyQualifiedContextString(); assertionBuilder = new AssertionLineBuilder(doc.ChildElements[2], templateIdentifierXpath, templateVersionIdentifierXpath); //"1..* id" rule.Assertions.Add(new Assertion() { AssertionMessage = "SHALL contain 1..* id (CONF:8654)", Test = assertionBuilder.WithCardinality(CardinalityParser.Parse("1..*")).WithinContext(contextBuilder.GetRelativeContextString()).ConformsTo(Conformance.SHALL).ToString() }); sectionCount++; pattern = new Pattern(); pattern.ID = sectionCount.ToString(); pattern.Name = string.Format("pattern-{0}-errors", pattern.ID); pattern.Rules.Add(rule); phase.ActivePatterns.Add(pattern); rule = new Rule(); contextBuilder = new ContextBuilder(doc.ChildElements[3], "cda"); rule.Context = contextBuilder.GetFullyQualifiedContextString(); assertionBuilder = new AssertionLineBuilder(doc.ChildElements[3], templateIdentifierXpath, templateVersionIdentifierXpath); //"1..1 code @xsi:type='CE' valueset = 2.16.840.1.113883.1.11.20.2" rule.Assertions.Add(new Assertion() { AssertionMessage = "SHALL contain 1..1 code with @xsi:type='CE', where the @code SHOULD be selected from ValueSet AdvanceDirectiveTypeCode 2.16.840.1.113883.1.11.20.2 STATIC 2006-10-17 (CONF:8651).", Test = assertionBuilder.WithCardinality(CardinalityParser.Parse("1..1")).WithinContext(contextBuilder.GetRelativeContextString()).ConformsTo(Conformance.SHALL).ToString() }); sectionCount++; pattern = new Pattern(); pattern.ID = sectionCount.ToString(); pattern.Name = string.Format("pattern-{0}-errors", pattern.ID); pattern.Rules.Add(rule); phase.ActivePatterns.Add(pattern); rule = new Rule(); contextBuilder = new ContextBuilder(doc.ChildElements[3], "cda"); rule.Context = contextBuilder.GetFullyQualifiedContextString(); assertionBuilder = new AssertionLineBuilder(doc.ChildElements[3], templateIdentifierXpath, templateVersionIdentifierXpath); //"1..1 statusCode @code='completed' valueset = 2.16.840.1.113883.1.11.20.2" rule.Assertions.Add(new Assertion() { AssertionMessage = "SHALL contain 1..1 code with @xsi:type='CE', where the @code SHOULD be selected from ValueSet AdvanceDirectiveTypeCode 2.16.840.1.113883.1.11.20.2 STATIC 2006-10-17 (CONF:8651).", Test = assertionBuilder.WithCardinality(CardinalityParser.Parse("1..1")).WithinContext(contextBuilder.GetRelativeContextString()).ConformsTo(Conformance.SHALL).ToString() }); sectionCount++; pattern = new Pattern(); pattern.ID = sectionCount.ToString(); pattern.Name = string.Format("pattern-{0}-errors", pattern.ID); pattern.Rules.Add(rule); phase.ActivePatterns.Add(pattern); rule = new Rule(); contextBuilder = new ContextBuilder(doc.ChildElements[1].Attributes[0], "cda"); rule.Context = contextBuilder.GetFullyQualifiedContextString(); var childtemplateIdElementAssertionBuilder = new AssertionLineBuilder(templateIdElement.Attributes[0], templateIdentifierXpath, templateVersionIdentifierXpath) //templateId/@root .WithCardinality(CardinalityParser.Parse("1..1")) .ConformsTo(Conformance.SHALL) .WithinContext("cda:"); var childParticipantElementAssertionBuilder = new AssertionLineBuilder(participantRoleElement, templateIdentifierXpath, templateVersionIdentifierXpath) .WithCardinality(CardinalityParser.Parse("1..*")) .ConformsTo(Conformance.SHALL) .WithinContext("cda:"); var childTimeElementAssertionBuilder = new AssertionLineBuilder(timeElement, templateIdentifierXpath, templateVersionIdentifierXpath) .WithCardinality(CardinalityParser.Parse("0..1")) .ConformsTo(Conformance.SHOULD) .WithinContext("cda:"); assertionBuilder = new AssertionLineBuilder(participantElement, templateIdentifierXpath, templateVersionIdentifierXpath); //participant rule.Assertions.Add(new Assertion() { AssertionMessage = "should contain 1..* participant (CONF:8662), participant should contain 0..1 time (CONF:8665), the data type of Observation/participant/time in a verification SHALL be TS (time stamp) (CONF:8666), participant shall contain 1..1 participantRole (CONF:8825), participant shall contain 1..1 @typeCode=VRF 'Verifier' (CodeSystem: 2.16.840.1.113883.5.90) (CONF:8663), participant shall contain 1..1 templateId (CONF:8664), templateId shall contain 1..1 @root=2.16.840.1.113883.10.20.1.58 (CONF:10486)", Test = assertionBuilder .WithCardinality(CardinalityParser.Parse("1..*")) .WithinContext("cda:") .ConformsTo(Conformance.SHALL) .WithChildElementBuilder(childTimeElementAssertionBuilder) .WithChildElementBuilder(childParticipantElementAssertionBuilder) .WithChildElementBuilder(childtemplateIdElementAssertionBuilder) .ToString() }); sectionCount++; pattern = new Pattern(); pattern.ID = sectionCount.ToString(); pattern.Name = string.Format("pattern-{0}-errors", pattern.ID); pattern.Rules.Add(rule); phase.ActivePatterns.Add(pattern); var builder = new SchematronDocumentSerializer(); string serializedModel = builder.SerializeDocument(document); Assert.IsFalse(string.IsNullOrEmpty(serializedModel), "No string returned from serialize document"); string[] lModelLines = serializedModel.Split('\n'); Assert.IsNotNull(lModelLines, "The generated string was not split on lines"); Assert.IsTrue(lModelLines.Length > 1, "The generated string was not split on lines"); }
public void GenerateSchematronAssertion_MultipleNestedElement_ConformanceSHALL_AND_SHOULD_CardinalityOneToOne_AND_ZeroToOne_For_Advance_Directive_Number9Constraint() { var doc = new DocumentTemplate("cda"); doc.AddElement(new DocumentTemplateElement("observation")); var participantElement = new DocumentTemplateElement("participant"); participantElement.AddAttribute(new DocumentTemplateElementAttribute("typeCode", "CST")); var participantRoleElement = new DocumentTemplateElement("participantRole"); participantRoleElement.AddAttribute(new DocumentTemplateElementAttribute("classCode", "AGNT")); participantElement.AddElement(participantRoleElement); var addrElement = new DocumentTemplateElement("addr"); participantRoleElement.AddElement(addrElement); var telecomElement = new DocumentTemplateElement("telecom"); participantRoleElement.AddElement(telecomElement); var playingEntityElement = new DocumentTemplateElement("playingEntity"); participantRoleElement.AddElement(playingEntityElement); var nameElement = new DocumentTemplateElement("name"); playingEntityElement.AddElement(nameElement); var participantRoleChildAssertionBuilder = new AssertionLineBuilder(this.tdb, participantRoleElement, this.igType, this.igTypeSchema) .WithinContext("cda:") .WithCardinality(CardinalityParser.Parse("1..1")) .ConformsTo(Conformance.SHALL); var addrChildAssertionBuilder = new AssertionLineBuilder(this.tdb, addrElement, this.igType, this.igTypeSchema) .WithinContext("cda:") .WithCardinality(CardinalityParser.Parse("0..1")) .ConformsTo(Conformance.SHALL); var telecomChildAssertionBuilder = new AssertionLineBuilder(this.tdb, telecomElement, this.igType, this.igTypeSchema) .WithinContext("cda:") .WithCardinality(CardinalityParser.Parse("0..1")) .ConformsTo(Conformance.SHALL); var nameChildAssertionBuilder = new AssertionLineBuilder(this.tdb, nameElement, this.igType, this.igTypeSchema) .WithinContext("cda:") .WithCardinality(CardinalityParser.Parse("1..1")) .ConformsTo(Conformance.SHALL); var playingEntityChildAssertionBuilder = new AssertionLineBuilder(this.tdb, playingEntityElement, this.igType, this.igTypeSchema) .WithinContext("cda:") .WithCardinality(CardinalityParser.Parse("1..1")) .WithChildElementBuilder(nameChildAssertionBuilder) //nested child assertion builder .ConformsTo(Conformance.SHALL); var assertionBuilder = new AssertionLineBuilder(this.tdb, participantElement, this.igType, this.igTypeSchema); //participant var assertion = assertionBuilder .WithCardinality(CardinalityParser.Parse("1..1")) .WithinContext("cda:") .ConformsTo(Conformance.SHALL) .WithChildElementBuilder(participantRoleChildAssertionBuilder) .WithChildElementBuilder(addrChildAssertionBuilder) .WithChildElementBuilder(telecomChildAssertionBuilder) .WithChildElementBuilder(playingEntityChildAssertionBuilder) .ToString(); var expected = "count(cda:participant[@typeCode='CST'][count(cda:participantRole[@classCode='AGNT'])=1][count(cda:addr) < 2][count(cda:telecom) < 2][count(cda:playingEntity[count(cda:name)=1])=1])=1"; Assert.IsTrue(assertion == expected, "Assertion string was not correct. Expected '{0}', Actual '{1}'", expected, assertion); }