Merge pull request #1396 from hapifhir/2023-08-gg-tx-tests-externals

2023 08 gg tx tests externals
This commit is contained in:
Grahame Grieve 2023-08-19 10:40:21 +10:00 committed by GitHub
commit 12e38a3ebe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1430 additions and 607 deletions

View File

@ -11,6 +11,8 @@ import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Scanner;
import org.hl7.fhir.exceptions.FHIRException;
@ -56,19 +58,23 @@ public class CPTImporter {
cs.setCopyright("CPT © Copyright 2019 American Medical Association. All rights reserved. AMA and CPT are registered trademarks of the American Medical Association.");
cs.addProperty().setCode("modifier").setDescription("Whether code is a modifier code").setType(PropertyType.BOOLEAN);
cs.addProperty().setCode("modified").setDescription("Whether code has been modified (all base codes are not modified)").setType(PropertyType.BOOLEAN);
cs.addProperty().setCode("orthopox").setDescription("Whether code is one of the Pathology and Laboratory and Immunization Code(s) for Orthopoxvirus").setType(PropertyType.BOOLEAN);
cs.addProperty().setCode("telemedicine").setDescription("Whether code is appropriate for use with telemedicine (and the telemedicine modifier)").setType(PropertyType.BOOLEAN);
cs.addProperty().setCode("kind").setDescription("Kind of Code (see metadata)").setType(PropertyType.CODE);
defineMetadata(cs);
System.out.println(readCodes(cs, Utilities.path(src, "LONGULT.txt"), false, null, null));
System.out.println(readCodes(cs, Utilities.path(src, "LONGUT.txt"), false, "upper", null));
System.out.println(readCodes(cs, Utilities.path(src, "MEDU.txt"), false, "med", null));
System.out.println(readCodes(cs, Utilities.path(src, "SHORTU.txt"), false, "short", null));
System.out.println(readCodes(cs, Utilities.path(src, "ConsumerDescriptor.txt"), true, "consumer", null));
System.out.println(readCodes(cs, Utilities.path(src, "ClinicianDescriptor.txt"), true, "clinician", null));
System.out.println(readCodes(cs, Utilities.path(src, "OrthopoxvirusCodes.txt"), false, null, "orthopod"));
System.out.println("LONGULT: "+readCodes(cs, Utilities.path(src, "LONGULT.txt"), false, null, null, null));
System.out.println("LONGUT: "+readCodes(cs, Utilities.path(src, "LONGUT.txt"), false, "upper", null, null));
System.out.println("MEDU: "+readCodes(cs, Utilities.path(src, "MEDU.txt"), false, "med", null, null));
System.out.println("SHORTU: "+readCodes(cs, Utilities.path(src, "SHORTU.txt"), false, "short", null, null));
System.out.println("ConsumerDescriptor: "+readCodes(cs, Utilities.path(src, "ConsumerDescriptor.txt"), true, "consumer", null, null));
System.out.println("ClinicianDescriptor: "+readCodes(cs, Utilities.path(src, "ClinicianDescriptor.txt"), true, "clinician", null, null));
System.out.println("OrthopoxvirusCodes: "+readCodes(cs, Utilities.path(src, "OrthopoxvirusCodes.txt"), false, null, null, "orthopox"));
System.out.println(processModifiers(cs, Utilities.path(src, "modifiers.csv")));
System.out.println("modifiers: "+processModifiers(cs, Utilities.path(src, "modifiers.csv")));
System.out.println("appendix P: "+processAppendixP(cs));
System.out.println("-------------------");
System.out.println(cs.getConcept().size());
@ -87,11 +93,59 @@ public class CPTImporter {
produceDB(Utilities.changeFileExt(dst, ".db"), cs);
cs.setContent(CodeSystemContentMode.FRAGMENT);
cs.getConcept().removeIf(cc -> !Utilities.existsInList(cc.getCode(), "metadata-kinds", "metadata-designations", "99202", "99203", "0001A", "25", "P1", "1P", "F1"));
cs.getConcept().removeIf(cc -> !Utilities.existsInList(cc.getCode(), "metadata-kinds", "metadata-designations", "99202", "99203", "0001A", "99252", "25", "P1", "1P", "F1", "95"));
new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.changeFileExt(dst, "-fragment.json")), cs);
produceDB(Utilities.changeFileExt(dst, "-fragment.db"), cs);
}
private String processAppendixP(CodeSystem cs) {
List<String> tcodes = new ArrayList<>();
tcodes.add("90785");
tcodes.add("90791");
tcodes.add("90792");
tcodes.add("90832");
tcodes.add("90833");
tcodes.add("90834");
tcodes.add("90836");
tcodes.add("90837");
tcodes.add("90838");
tcodes.add("90839");
tcodes.add("90840");
tcodes.add("90845");
tcodes.add("90846");
tcodes.add("90847");
tcodes.add("92507");
tcodes.add("92508");
tcodes.add("92521");
tcodes.add("92522");
tcodes.add("92523");
tcodes.add("92524");
tcodes.add("96040");
tcodes.add("96110");
tcodes.add("96116");
tcodes.add("96160");
tcodes.add("96161");
tcodes.add("97802");
tcodes.add("97803");
tcodes.add("97804");
tcodes.add("99406");
tcodes.add("99407");
tcodes.add("99408");
tcodes.add("99409");
tcodes.add("99497");
tcodes.add("99498");
for (String c : tcodes) {
ConceptDefinitionComponent cc = CodeSystemUtilities.findCode(cs.getConcept(), c);
if (cc == null) {
throw new Error("unable to find tcode "+c);
}
cc.addProperty().setCode("telemedicine").setValue(new BooleanType(true));
}
return String.valueOf(tcodes.size());
}
private void produceDB(String path, CodeSystem cs) throws ClassNotFoundException, SQLException {
Connection con = connect(path);
@ -197,7 +251,6 @@ public class CPTImporter {
mm(pc.addConcept()).setCode("physical-status").setDisplay("Anesthesia Physical Status Modifiers");
mm(pc.addConcept()).setCode("general").setDisplay("A general modifier");
mm(pc.addConcept()).setCode("hcpcs").setDisplay("Level II (HCPCS/National) Modifiers");
mm(pc.addConcept()).setCode("orthopox").setDisplay("");
mm(pc.addConcept()).setCode("metadata").setDisplay("A kind of code or designation");
ConceptDefinitionComponent dc = mm(cs.addConcept().setCode("metadata-designations"));
@ -251,7 +304,7 @@ public class CPTImporter {
return res;
}
private int readCodes(CodeSystem cs, String path, boolean hasConceptId, String use, String type) throws IOException {
private int readCodes(CodeSystem cs, String path, boolean hasConceptId, String use, String type, String boolProp) throws IOException {
int res = 0;
FileInputStream inputStream = null;
Scanner sc = null;
@ -286,6 +339,9 @@ public class CPTImporter {
} else if (type != null) {
cc.addProperty().setCode("kind").setValue(new CodeType(type));
}
if (boolProp != null) {
cc.addProperty().setCode(boolProp).setValue(new BooleanType(true));
}
if (use == null) {
if (cc.hasDisplay()) {
System.err.println("?");

View File

@ -9,15 +9,13 @@ import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.ChangeAnalysisState;
import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
@ -257,53 +255,53 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
super(session);
}
protected boolean compareMetadata(CanonicalResource left, CanonicalResource right, Map<String, StructuralMatch<String>> comp, CanonicalResourceComparison<? extends CanonicalResource> res, List<String> changes, Base parent, String version) {
protected boolean compareMetadata(CanonicalResource left, CanonicalResource right, Map<String, StructuralMatch<String>> comp, CanonicalResourceComparison<? extends CanonicalResource> res, List<String> changes, Base parent) {
var changed = false;
if (comparePrimitivesWithTracking("url", left.getUrlElement(), right.getUrlElement(), comp, IssueSeverity.ERROR, res, parent, version)) {
if (comparePrimitivesWithTracking("url", left.getUrlElement(), right.getUrlElement(), comp, IssueSeverity.ERROR, res, parent)) {
changed = true;
changes.add("url");
}
if (session.getForVersion() == null) {
if (comparePrimitivesWithTracking("version", left.getVersionElement(), right.getVersionElement(), comp, IssueSeverity.ERROR, res, parent, version)) {
if (!session.isAnnotate()) {
if (comparePrimitivesWithTracking("version", left.getVersionElement(), right.getVersionElement(), comp, IssueSeverity.ERROR, res, parent)) {
changed = true;
changes.add("version");
}
}
if (comparePrimitivesWithTracking("name", left.getNameElement(), right.getNameElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) {
if (comparePrimitivesWithTracking("name", left.getNameElement(), right.getNameElement(), comp, IssueSeverity.INFORMATION, res, parent)) {
changed = true;
changes.add("name");
}
if (comparePrimitivesWithTracking("title", left.getTitleElement(), right.getTitleElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) {
if (comparePrimitivesWithTracking("title", left.getTitleElement(), right.getTitleElement(), comp, IssueSeverity.INFORMATION, res, parent)) {
changed = true;
changes.add("title");
}
if (comparePrimitivesWithTracking("status", left.getStatusElement(), right.getStatusElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) {
if (comparePrimitivesWithTracking("status", left.getStatusElement(), right.getStatusElement(), comp, IssueSeverity.INFORMATION, res, parent)) {
changed = true;
changes.add("status");
}
if (comparePrimitivesWithTracking("experimental", left.getExperimentalElement(), right.getExperimentalElement(), comp, IssueSeverity.WARNING, res, parent, version)) {
if (comparePrimitivesWithTracking("experimental", left.getExperimentalElement(), right.getExperimentalElement(), comp, IssueSeverity.WARNING, res, parent)) {
changed = true;
changes.add("experimental");
}
if (session.getForVersion() == null) {
if (comparePrimitivesWithTracking("date", left.getDateElement(), right.getDateElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) {
if (!session.isAnnotate()) {
if (comparePrimitivesWithTracking("date", left.getDateElement(), right.getDateElement(), comp, IssueSeverity.INFORMATION, res, parent)) {
changed = true;
changes.add("date");
}
}
if (comparePrimitivesWithTracking("publisher", left.getPublisherElement(), right.getPublisherElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) {
if (comparePrimitivesWithTracking("publisher", left.getPublisherElement(), right.getPublisherElement(), comp, IssueSeverity.INFORMATION, res, parent)) {
changed = true;
changes.add("publisher");
}
if (comparePrimitivesWithTracking("description", left.getDescriptionElement(), right.getDescriptionElement(), comp, IssueSeverity.NULL, res, parent, version)) {
if (comparePrimitivesWithTracking("description", left.getDescriptionElement(), right.getDescriptionElement(), comp, IssueSeverity.NULL, res, parent)) {
changed = true;
changes.add("description");
}
if (comparePrimitivesWithTracking("purpose", left.getPurposeElement(), right.getPurposeElement(), comp, IssueSeverity.NULL, res, parent, version)) {
if (comparePrimitivesWithTracking("purpose", left.getPurposeElement(), right.getPurposeElement(), comp, IssueSeverity.NULL, res, parent)) {
changed = true;
changes.add("purpose");
}
if (comparePrimitivesWithTracking("copyright", left.getCopyrightElement(), right.getCopyrightElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) {
if (comparePrimitivesWithTracking("copyright", left.getCopyrightElement(), right.getCopyrightElement(), comp, IssueSeverity.INFORMATION, res, parent)) {
changed = true;
changes.add("copyright");
}
@ -464,29 +462,59 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
}
protected boolean comparePrimitivesWithTracking(String name, List< ? extends PrimitiveType> ll, List<? extends PrimitiveType> rl, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, Base parent) {
boolean def = false;
List<PrimitiveType> matchR = new ArrayList<>();
for (PrimitiveType l : ll) {
PrimitiveType r = findInList(rl, l);
if (r == null) {
session.markDeleted(parent, "element", l);
} else {
matchR.add(r);
def = comparePrimitivesWithTracking(name, l, r, comp, level, res, parent) || def;
}
}
for (PrimitiveType r : rl) {
if (!matchR.contains(r)) {
session.markAdded(r);
}
}
return def;
}
private PrimitiveType findInList(List<? extends PrimitiveType> rl, PrimitiveType l) {
for (PrimitiveType r : rl) {
if (r.equalsDeep(l)) {
return r;
}
}
return null;
}
@SuppressWarnings("rawtypes")
protected boolean comparePrimitivesWithTracking(String name, PrimitiveType l, PrimitiveType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, Base parent, String version) {
protected boolean comparePrimitivesWithTracking(String name, PrimitiveType l, PrimitiveType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, Base parent) {
StructuralMatch<String> match = null;
if (l.isEmpty() && r.isEmpty()) {
match = new StructuralMatch<>(null, null, null);
} else if (l.isEmpty()) {
match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "Added the item '"+r.primitiveValue()+"'", fhirType()+"."+name));
VersionComparisonAnnotation.markAdded(r, version);
session.markAdded(r);
} else if (r.isEmpty()) {
match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "Removed the item '"+l.primitiveValue()+"'", fhirType()+"."+name));
VersionComparisonAnnotation.markDeleted(parent, version, name, l);
session.markDeleted(parent, name, l);
} else if (!l.hasValue() && !r.hasValue()) {
match = new StructuralMatch<>(null, null, vmI(IssueSeverity.INFORMATION, "No Value", fhirType()+"."+name));
} else if (!l.hasValue()) {
match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "No Value on Left", fhirType()+"."+name));
VersionComparisonAnnotation.markAdded(r, version);
session.markAdded(r);
} else if (!r.hasValue()) {
match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "No Value on Right", fhirType()+"."+name));
VersionComparisonAnnotation.markDeleted(parent, version, name, l);
session.markDeleted(parent, name, l);
} else if (l.getValue().equals(r.getValue())) {
match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), null);
} else {
VersionComparisonAnnotation.markChanged(r, version);
session.markChanged(r, l);
match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), vmI(level, "Values Differ", fhirType()+"."+name));
if (level != IssueSeverity.NULL && res != null) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.primitiveValue()+"' vs '"+r.primitiveValue()+"'", level));
@ -497,6 +525,65 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
}
return match.isDifferent();
}
protected boolean compareDataTypesWithTracking(String name, List< ? extends DataType> ll, List<? extends DataType> rl, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, Base parent) {
boolean def = false;
List<DataType> matchR = new ArrayList<>();
for (DataType l : ll) {
DataType r = findInList(rl, l);
if (r == null) {
session.markDeleted(parent, "element", l);
} else {
matchR.add(r);
def = compareDataTypesWithTracking(name, l, r, comp, level, res, parent) || def;
}
}
for (DataType r : rl) {
if (!matchR.contains(r)) {
session.markAdded(r);
}
}
return def;
}
private DataType findInList(List<? extends DataType> rl, DataType l) {
for (DataType r : rl) {
if (r.equalsDeep(l)) {
return r;
}
}
return null;
}
@SuppressWarnings("rawtypes")
protected boolean compareDataTypesWithTracking(String name, DataType l, DataType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, Base parent) {
StructuralMatch<String> match = null;
boolean le = l == null || l.isEmpty();
boolean re = r == null || r.isEmpty();
if (le && re) {
match = new StructuralMatch<>(null, null, null);
} else if (le) {
match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "Added the item '"+r.fhirType()+"'", fhirType()+"."+name));
session.markAdded(r);
} else if (re) {
match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "Removed the item '"+l.fhirType()+"'", fhirType()+"."+name));
session.markDeleted(parent, name, l);
} else if (l.equalsDeep(r)) {
match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), null);
} else {
session.markChanged(r, l);
match = new StructuralMatch<>(l.fhirType(), r.fhirType(), vmI(level, "Values Differ", fhirType()+"."+name));
if (level != IssueSeverity.NULL && res != null) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.fhirType()+"' vs '"+r.fhirType()+"'", level));
}
}
if (comp != null) {
comp.put(name, match);
}
return match.isDifferent();
}
protected abstract String fhirType();

View File

@ -3,17 +3,13 @@ package org.hl7.fhir.r5.comparison;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison;
import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.BackboneElement;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CanonicalType;
@ -24,13 +20,10 @@ import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResource
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestSecurityComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent;
import org.hl7.fhir.r5.model.CapabilityStatement.ResourceVersionPolicy;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.Element;
import org.hl7.fhir.r5.model.Enumeration;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Resource;
@ -114,7 +107,7 @@ public class CapabilityStatementComparer extends CanonicalResourceComparer {
cs1.setStatus(left.getStatus());
cs1.setDate(new Date());
compareMetadata(left, right, res.getMetadata(), res, new ArrayList<>(), right, session.getForVersion());
compareMetadata(left, right, res.getMetadata(), res, new ArrayList<>(), right);
comparePrimitives("kind", left.getKindElement(), right.getKindElement(), res.getMetadata(), IssueSeverity.ERROR, res);
compareCanonicalList("instantiates", left.getInstantiates(), right.getInstantiates(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getInstantiates(), cs1.getInstantiates());
compareCanonicalList("imports", left.getImports(), right.getImports(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getImports(), cs1.getImports());

View File

@ -9,7 +9,6 @@ import java.util.Map;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
@ -132,7 +131,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
List<String> chMetadata = new ArrayList<>();
boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right, session.getForVersion());
boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right);
if (comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res)) {
ch = true;
chMetadata.add("versionNeeded");
@ -143,16 +142,16 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
}
res.updatedMetadataState(ch, chMetadata);
ch = false;
ch = comparePrimitivesWithTracking("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res, right, session.getForVersion()) || ch;
ch = comparePrimitivesWithTracking("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res, right, session.getForVersion()) || ch;
ch = comparePrimitivesWithTracking("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res, right, session.getForVersion());
ch = comparePrimitivesWithTracking("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch;
ch = comparePrimitivesWithTracking("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch;
ch = comparePrimitivesWithTracking("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res, right);
ch = compareProperties(left.getProperty(), right.getProperty(), res.getProperties(), res.getUnion().getProperty(), res.getIntersection().getProperty(), res.getUnion(), res.getIntersection(), res, "CodeSystem.property", right) || ch;
ch = compareFilters(left.getFilter(), right.getFilter(), res.getFilters(), res.getUnion().getFilter(), res.getIntersection().getFilter(), res.getUnion(), res.getIntersection(), res, "CodeSystem.filter", right) || ch;
ch = compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept", right) || ch;
res.updateDefinitionsState(ch);
VersionComparisonAnnotation.annotate(right, session.getForVersion(), res);
session.annotate(right, res);
return res;
}
@ -193,7 +192,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
union.add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "concept", l);
session.markDeleted(parent, "concept", l);
} else {
matchR.add(r);
PropertyComponent cdM = merge(l, r, res);
@ -212,7 +211,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
union.add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
session.markAdded(r);
}
}
return def;
@ -229,7 +228,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
union.add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "concept", l);
session.markDeleted(parent, "concept", l);
} else {
matchR.add(r);
CodeSystemFilterComponent cdM = merge(l, r, res);
@ -248,7 +247,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
union.add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
session.markAdded(r);
}
}
return def;
@ -265,7 +264,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
union.add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "concept", l);
session.markDeleted(parent, "concept", l);
} else {
matchR.add(r);
ConceptDefinitionComponent cdM = merge(l, r, csU.getProperty(), res);
@ -287,7 +286,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
union.add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
session.markAdded(r);
}
}
return def;
@ -351,19 +350,19 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
if (!Utilities.noString(right)) {
if (Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" added", path));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
session.markAdded(r);
return true;
} else if (!left.equals(right)) {
if (level != IssueSeverity.NULL) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
}
msgs.add(vmI(level, name+" changed from left to right", path));
VersionComparisonAnnotation.markChanged(r, session.getForVersion());
session.markChanged(r, l);
return true;
}
} else if (!Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" removed", path));
VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "concept", l);
session.markDeleted(parent, "concept", l);
return true;
}
return false;

View File

@ -16,11 +16,10 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.r5.comparison.CapabilityStatementComparer.CapabilityStatementComparison;
import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison;
import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison;
import org.hl7.fhir.r5.comparison.ResourceComparer.PlaceHolderComparison;
import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison;
import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison;
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
@ -28,7 +27,6 @@ import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.Tuple;
import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;

View File

@ -8,14 +8,17 @@ import java.util.UUID;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.CanonicalResourceComparison;
import org.hl7.fhir.r5.comparison.CapabilityStatementComparer.CapabilityStatementComparison;
import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison;
import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison;
import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison;
import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison;
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation.AnotationType;
import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.CodeSystem;
@ -32,7 +35,6 @@ public class ComparisonSession {
private String sessiondId;
private int count;
private boolean debug;
private String forVersion;
private boolean annotate;
private String title;
private ProfileKnowledgeProvider pkpLeft;
@ -115,7 +117,7 @@ public class ComparisonSession {
return csc;
}
} else if (left != null) {
VersionComparisonAnnotation.markDeleted(null, forVersion, left.fhirType(), left); // todo: waht?
markDeleted(null, left.fhirType(), left); // todo: waht?
String key = key(left.getUrl(), left.getVersion(), left.getUrl(), left.getVersion());
if (compares.containsKey(key)) {
return compares.get(key);
@ -124,7 +126,7 @@ public class ComparisonSession {
compares.put(key, csc);
return csc;
} else {
VersionComparisonAnnotation.markAdded(right, forVersion);
markAdded(right);
String key = key(right.getUrl(), right.getVersion(), right.getUrl(), right.getVersion());
if (compares.containsKey(key)) {
return compares.get(key);
@ -169,14 +171,6 @@ public class ComparisonSession {
return pkpRight;
}
public String getForVersion() {
return forVersion;
}
public void setForVersion(String forVersion) {
this.forVersion = forVersion;
}
public boolean isAnnotate() {
return annotate;
}
@ -184,6 +178,39 @@ public class ComparisonSession {
public void setAnnotate(boolean annotate) {
this.annotate = annotate;
}
private VersionComparisonAnnotation getAnnotation(Base b) {
if (b.hasUserData(VersionComparisonAnnotation.USER_DATA_NAME)) {
return (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME);
} else {
VersionComparisonAnnotation vca = new VersionComparisonAnnotation(AnotationType.NoChange);
b.setUserData(VersionComparisonAnnotation.USER_DATA_NAME, vca);
return vca;
}
}
public void markAdded(Base focus) {
if (isAnnotate()) {
getAnnotation(focus).added();
}
}
public void markChanged(Base focus, Base original) {
if (isAnnotate()) {
getAnnotation(focus).changed(original);
}
}
public void markDeleted(Base parent, String name, Base other) {
if (isAnnotate() && other != null) {
getAnnotation(parent).deleted(name, other);
getAnnotation(other).deleted();
}
}
public void annotate(Base base, CanonicalResourceComparison<? extends CanonicalResource> comp) {
if (isAnnotate()) {
getAnnotation(base).comp(comp);
}
}
}

View File

@ -3,8 +3,6 @@ package org.hl7.fhir.r5.comparison;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -326,5 +324,7 @@ public class ResourceComparer {
}
return cell;
}
}

View File

@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;

View File

@ -5,12 +5,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.CanonicalResourceComparison;
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r5.conformance.profile.BindingResolution;
import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
@ -19,7 +17,6 @@ import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.ElementDefinition;
@ -43,10 +40,8 @@ import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
import org.hl7.fhir.r5.utils.DefinitionNavigator;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
@ -147,7 +142,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
sd1.setDate(new Date());
List<String> chMetadata = new ArrayList<>();
boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right, session.getForVersion());
boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right);
if (comparePrimitives("fhirVersion", left.getFhirVersionElement(), right.getFhirVersionElement(), res.getMetadata(), IssueSeverity.WARNING, res)) {
ch = true;
chMetadata.add("fhirVersion");
@ -178,7 +173,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
}
res.updateDefinitionsState(ch);
VersionComparisonAnnotation.annotate(right, session.getForVersion(), res);
session.annotate(right, res);
return res;
}
@ -228,19 +223,19 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
// descriptive properties from ElementDefinition - merge them:
subset.setLabel(mergeText(comp, res, path, "label", left.current().getLabel(), right.current().getLabel(), false));
comparePrimitivesWithTracking("label", left.current().getLabelElement(), right.current().getLabelElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion());
comparePrimitivesWithTracking("label", left.current().getLabelElement(), right.current().getLabelElement(), null, IssueSeverity.INFORMATION, comp, right.current());
subset.setShort(mergeText(comp, res, path, "short", left.current().getShort(), right.current().getShort(), false));
def = comparePrimitivesWithTracking("short", left.current().getShortElement(), right.current().getShortElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("short", left.current().getShortElement(), right.current().getShortElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def;
subset.setDefinition(mergeText(comp, res, path, "definition", left.current().getDefinition(), right.current().getDefinition(), false));
def = comparePrimitivesWithTracking("definition", left.current().getDefinitionElement(), right.current().getDefinitionElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("definition", left.current().getDefinitionElement(), right.current().getDefinitionElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def;
subset.setComment(mergeText(comp, res, path, "comments", left.current().getComment(), right.current().getComment(), false));
def = comparePrimitivesWithTracking("comment", left.current().getCommentElement(), right.current().getCommentElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("comment", left.current().getCommentElement(), right.current().getCommentElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def;
subset.setRequirements(mergeText(comp, res, path, "requirements", left.current().getRequirements(), right.current().getRequirements(), false));
def = comparePrimitivesWithTracking("requirements", left.current().getRequirementsElement(), right.current().getRequirementsElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("requirements", left.current().getRequirementsElement(), right.current().getRequirementsElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def;
subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode()));
subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias()));
@ -253,12 +248,12 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
}
subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport());
def = comparePrimitivesWithTracking("mustSupport", left.current().getMustSupportElement(), right.current().getMustSupportElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("mustSupport", left.current().getMustSupportElement(), right.current().getMustSupportElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def;
ElementDefinition superset = subset.copy();
def = comparePrimitivesWithTracking("min", left.current().getMinElement(), right.current().getMinElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("max", left.current().getMaxElement(), right.current().getMaxElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("min", left.current().getMinElement(), right.current().getMinElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def;
def = comparePrimitivesWithTracking("max", left.current().getMaxElement(), right.current().getMaxElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def;
// compare and intersect
int leftMin = left.current().getMin();
@ -396,6 +391,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
assert(left.path().equals(right.path()));
boolean def = false;
boolean ch = false;
// not allowed to be different:
// ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path);
@ -403,25 +399,71 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
// ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - this check belongs in the core
// ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); - so does this
if (left.current() == null && right.current() == null) {
ElementDefinition edl = left.current();
ElementDefinition edr = right.current();
if (edl == null && edr == null) {
// both are sparse at this point, do nothing
} else if (left.current() == null) {
VersionComparisonAnnotation.markAdded(right.current(), session.getForVersion());
} else if (right.current() == null) {
VersionComparisonAnnotation.markDeleted(right.parent(), session.getForVersion(), "element", left.current());
} else if (edl == null) {
session.markAdded(edr);
} else if (edr == null) {
session.markDeleted(right.parent(), "element", edl);
} else {
// descriptive properties from ElementDefinition - merge them:
comparePrimitivesWithTracking("label", left.current().getLabelElement(), right.current().getLabelElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion());
def = comparePrimitivesWithTracking("short", left.current().getShortElement(), right.current().getShortElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("definition", left.current().getDefinitionElement(), right.current().getDefinitionElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("comment", left.current().getCommentElement(), right.current().getCommentElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("requirements", left.current().getRequirementsElement(), right.current().getRequirementsElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("mustSupport", left.current().getMustSupportElement(), right.current().getMustSupportElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("min", left.current().getMinElement(), right.current().getMinElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
def = comparePrimitivesWithTracking("max", left.current().getMaxElement(), right.current().getMaxElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
// descriptive properties from ElementDefinition
comparePrimitivesWithTracking("label", edl.getLabelElement(), edr.getLabelElement(), null, IssueSeverity.INFORMATION, null, edr);
comparePrimitivesWithTracking("sliceName", edl.getSliceNameElement(), edr.getSliceNameElement(), null, IssueSeverity.INFORMATION, null, edr);
comparePrimitivesWithTracking("sliceIsConstraining", edl.getSliceIsConstrainingElement(), edr.getSliceIsConstrainingElement(), null, IssueSeverity.INFORMATION, null, edr);
comparePrimitivesWithTracking("alias", edl.getAlias(), edr.getAlias(), null, IssueSeverity.INFORMATION, null, edr);
compareDataTypesWithTracking("code", edl.getCode(), edr.getCode(), null, IssueSeverity.INFORMATION, null, edr);
def = comparePrimitivesWithTracking("short", edl.getShortElement(), edr.getShortElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
def = comparePrimitivesWithTracking("definition", edl.getDefinitionElement(), edr.getDefinitionElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
def = comparePrimitivesWithTracking("comment", edl.getCommentElement(), edr.getCommentElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
def = comparePrimitivesWithTracking("requirements", edl.getRequirementsElement(), edr.getRequirementsElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
def = comparePrimitivesWithTracking("mustSupport", edl.getMustSupportElement(), edr.getMustSupportElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
def = comparePrimitivesWithTracking("meaningWhenMissing", edl.getMeaningWhenMissingElement(), edr.getMeaningWhenMissingElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
def = comparePrimitivesWithTracking("isModifierReason", edl.getIsModifierReasonElement(), edr.getIsModifierReasonElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
ch = comparePrimitivesWithTracking("min", edl.getMinElement(), edr.getMinElement(), null, IssueSeverity.ERROR, null, edr) || ch;
ch = comparePrimitivesWithTracking("max", edl.getMaxElement(), edr.getMaxElement(), null, IssueSeverity.ERROR, null, edr) || ch;
ch = compareDataTypesWithTracking("defaultValue", edl.getDefaultValue(), edr.getDefaultValue(), null, IssueSeverity.ERROR, null, edr) || ch;
ch = compareDataTypesWithTracking("fixed", edl.getFixed(), edr.getFixed(), null, IssueSeverity.ERROR, null, edr) || ch;
ch = compareDataTypesWithTracking("pattern", edl.getPattern(), edr.getPattern(), null, IssueSeverity.ERROR, null, edr) || ch;
ch = compareDataTypesWithTracking("minValue", edl.getMinValue(), edr.getMinValue(), null, IssueSeverity.ERROR, null, edr) || ch;
ch = compareDataTypesWithTracking("maxValue", edl.getMaxValue(), edr.getMaxValue(), null, IssueSeverity.ERROR, null, edr) || ch;
ch = comparePrimitivesWithTracking("maxLength", edl.getMaxLengthElement(), edr.getMaxLengthElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
ch = comparePrimitivesWithTracking("mustHaveValue", edl.getMustHaveValueElement(), edr.getMustHaveValueElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
ch = comparePrimitivesWithTracking("valueAlternatives", edl.getValueAlternatives(), edr.getValueAlternatives(), null, IssueSeverity.INFORMATION, null, edr) || ch;
ch = comparePrimitivesWithTracking("isModifier", edl.getIsModifierElement(), edr.getIsModifierElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
def = compareTypes(path, sliceName, edl, edr, res) || def;
ElementDefinitionBindingComponent bl = edl.getBinding();
ElementDefinitionBindingComponent br = edr.getBinding();
if (bl == null && br == null) {
// both are sparse at this point, do nothing
} else if (bl == null) {
session.markAdded(edr);
} else if (br == null) {
session.markDeleted(right.parent(), "element", edl);
} else {
ch = comparePrimitivesWithTracking("strength", bl.getStrengthElement(), br.getStrengthElement(), null, IssueSeverity.ERROR, null, edr) || ch;
def = comparePrimitivesWithTracking("description", bl.getDescriptionElement(), br.getDescriptionElement(), null, IssueSeverity.ERROR, null, edr) || def;
ch = comparePrimitivesWithTracking("valueSet", bl.getValueSetElement(), br.getValueSetElement(), null, IssueSeverity.ERROR, null, edr) || ch;
// todo: additional
}
def = compareInvariants(path, sliceName, edl, edr, res) || def;
// main todos:
// invariants, slicing
// mappings
}
// add the children
def = compareDiffChildren(path, left, right, right.current() == null ? parent : right.current(), res) || def;
if (ch) {
res.updateContentState(true);
}
def = compareDiffChildren(path, left, right, edr == null ? parent : edr, res) || def;
//
// // now process the slices
// if (left.current().hasSlicing() || right.current().hasSlicing()) {
@ -484,7 +526,6 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
return def;
}
private boolean compareDiffChildren(String path, DefinitionNavigator left, DefinitionNavigator right, Base parent, ProfileComparison res) throws DefinitionException, IOException, FHIRFormatError {
boolean def = false;
@ -495,7 +536,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
for (DefinitionNavigator l : lc) {
DefinitionNavigator r = findInList(rc, l);
if (r == null) {
VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "element", l.current());
session.markDeleted(parent, "element", l.current());
res.updateContentState(true);
} else {
matchR.add(r);
@ -504,7 +545,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
}
for (DefinitionNavigator r : rc) {
if (!matchR.contains(r)) {
VersionComparisonAnnotation.markAdded(r.current(), session.getForVersion());
session.markAdded(r.current());
res.updateContentState(true);
}
}
@ -512,14 +553,112 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
}
private DefinitionNavigator findInList(List<DefinitionNavigator> rc, DefinitionNavigator l) {
String s = l.getId();
for (DefinitionNavigator t : rc) {
if (tail(t.current().getPath()).equals(tail(l.current().getPath()))) {
String ts = t.getId();
if (tail(ts).equals(tail(s))) {
return t;
}
}
return null;
}
private boolean compareTypes(String path, String sliceName, ElementDefinition left, ElementDefinition right, ProfileComparison res) {
boolean def = false;
List<TypeRefComponent> matchR = new ArrayList<>();
for (TypeRefComponent l : left.getType()) {
TypeRefComponent r = findInList(right.getType(), l);
if (r == null) {
session.markDeleted(right, "type", l);
res.updateContentState(true);
} else {
matchR.add(r);
def = compareType(path+".type", l, r, res) || def;
}
}
for (TypeRefComponent r : right.getType()) {
if (!matchR.contains(r)) {
session.markAdded(r);
res.updateContentState(true);
}
}
return def;
}
private TypeRefComponent findInList(List<TypeRefComponent> rc, TypeRefComponent l) {
for (TypeRefComponent t : rc) {
if (t.getCodeElement().equalsDeep(l.getCodeElement())) {
return t;
}
}
return null;
}
private boolean compareType(String string, TypeRefComponent l, TypeRefComponent r, ProfileComparison res) {
boolean def = false;
boolean ch = false;
// codes must match
ch = comparePrimitivesWithTracking("profile", l.getProfile(), r.getProfile(), null, IssueSeverity.ERROR, null, r) || ch;
ch = comparePrimitivesWithTracking("targetProfile", l.getTargetProfile(), r.getTargetProfile(), null, IssueSeverity.ERROR, null, r) || ch;
ch = comparePrimitivesWithTracking("aggregation", l.getAggregation(), r.getAggregation(), null, IssueSeverity.ERROR, null, r) || ch;
def = comparePrimitivesWithTracking("versioning", l.getVersioningElement(), r.getVersioningElement(), null, IssueSeverity.INFORMATION, null, r) || def;
if (ch) {
res.updateContentState(true);
}
return def;
}
private boolean compareInvariants(String path, String sliceName, ElementDefinition left, ElementDefinition right, ProfileComparison res) {
boolean def = false;
List<ElementDefinitionConstraintComponent> matchR = new ArrayList<>();
for (ElementDefinitionConstraintComponent l : left.getConstraint()) {
ElementDefinitionConstraintComponent r = findInList(right.getConstraint(), l);
if (r == null) {
session.markDeleted(right, "invariant", l);
res.updateContentState(true);
} else {
matchR.add(r);
def = compareInvariant(path+".type", l, r, res) || def;
}
}
for (ElementDefinitionConstraintComponent r : right.getConstraint()) {
if (!matchR.contains(r)) {
session.markAdded(r);
res.updateContentState(true);
}
}
return def;
}
private ElementDefinitionConstraintComponent findInList(List<ElementDefinitionConstraintComponent> rc, ElementDefinitionConstraintComponent l) {
for (ElementDefinitionConstraintComponent t : rc) {
if (t.getKeyElement().equalsDeep(l.getKeyElement())) {
return t;
}
}
return null;
}
private boolean compareInvariant(String string, ElementDefinitionConstraintComponent l, ElementDefinitionConstraintComponent r, ProfileComparison res) {
boolean def = false;
boolean ch = false;
// codes must match
def = comparePrimitivesWithTracking("requirements", l.getRequirementsElement(), r.getRequirementsElement(), null, IssueSeverity.INFORMATION, null, r) || def;
ch = comparePrimitivesWithTracking("severity", l.getSeverityElement(), r.getSeverityElement(), null, IssueSeverity.ERROR, null, r) || ch;
comparePrimitivesWithTracking("suppress", l.getSuppressElement(), r.getSuppressElement(), null, IssueSeverity.INFORMATION, null, r);
def = comparePrimitivesWithTracking("human", l.getHumanElement(), r.getHumanElement(), null, IssueSeverity.INFORMATION, null, r) || def;
ch = comparePrimitivesWithTracking("expression", l.getExpressionElement(), r.getExpressionElement(), null, IssueSeverity.ERROR, null, r) || ch;
if (ch) {
res.updateContentState(true);
}
return def;
}
// private void ruleEqual(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, DataType vLeft, DataType vRight, String name, String path) throws IOException {
// if (vLeft == null && vRight == null) {
// // nothing

View File

@ -7,8 +7,6 @@ import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation.AnotationType;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.Element;
@ -125,7 +123,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
vs1.setDate(new Date());
List<String> chMetadata = new ArrayList<>();
var ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right, session.getForVersion());
var ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right);
var def = false;
if (comparePrimitives("immutable", left.getImmutableElement(), right.getImmutableElement(), res.getMetadata(), IssueSeverity.WARNING, res)) {
ch = true;
@ -143,7 +141,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
def = compareCompose(left.getCompose(), right.getCompose(), res, res.getUnion().getCompose(), res.getIntersection().getCompose()) || def;
res.updateDefinitionsState(def);
// compareExpansions(left, right, res);
VersionComparisonAnnotation.annotate(right, session.getForVersion(), res);
session.annotate(right, res);
return res;
}
@ -157,7 +155,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
union.getInclude().add(l);
res.updateContentState(true);
res.getIncludes().getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed Include", "ValueSet.compose.include")));
VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "include", l);
session.markDeleted(right, "include", l);
} else {
matchR.add(r);
ConceptSetComponent csM = new ConceptSetComponent();
@ -174,7 +172,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
union.getInclude().add(r);
res.updateContentState(true);
res.getIncludes().getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added Include", "ValueSet.compose.include"), r));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
session.markAdded(r);
}
}
@ -252,7 +250,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.ERROR, "Removed ValueSet", "ValueSet.compose.include.valueSet")));
if (session.isAnnotate()) {
VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "valueset", l);
session.markDeleted(right, "valueset", l);
}
} else {
matchVSR.add(r);
@ -269,7 +267,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.WARNING, "Values are different", "ValueSet.compose.include.valueSet"));
combined.getChildren().add(sm);
if (session.isAnnotate()) {
VersionComparisonAnnotation.markChanged(r, session.getForVersion());
session.markChanged(r, l);
}
}
@ -280,7 +278,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
union.getValueSet().add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.ERROR, "Add ValueSet", "ValueSet.compose.include.valueSet"), r));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
session.markAdded(r);
}
}
@ -292,7 +290,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.ERROR, "Removed this Concept", "ValueSet.compose.include.concept")));
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" removed", IssueSeverity.ERROR));
VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "concept", l);
session.markDeleted(right,"concept", l);
} else {
matchCR.add(r);
if (l.getCode().equals(r.getCode())) {
@ -311,7 +309,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
combined.getChildren().add(sm);
res.updateContentState(true);
compareConcepts(path+".concept["+right.getConcept().indexOf(r)+"]", l, r, sm, null, null, res);
VersionComparisonAnnotation.markChanged(r, session.getForVersion());
session.markChanged(r, l);
}
}
}
@ -321,7 +319,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.ERROR, "Added this Concept", "ValueSet.compose.include.concept"), r));
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+r.getCode()+" added", IssueSeverity.ERROR));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
session.markAdded(r);
}
}
@ -332,7 +330,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
union.getFilter().add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.ERROR, "Removed this item", "ValueSet.compose.include.filter")));
VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "filter", l);
session.markDeleted(right, "filter", l);
} else {
matchFR.add(r);
if (l.getProperty().equals(r.getProperty()) && l.getOp().equals(r.getOp())) {
@ -344,7 +342,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
combined.getChildren().add(sm);
if (!compareFilters(l, r, sm, cu, ci)) {
res.updateContentState(true);
VersionComparisonAnnotation.markChanged(r, session.getForVersion());
session.markChanged(r, l);
}
} else {
union.getFilter().add(l);
@ -361,7 +359,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
union.getFilter().add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.ERROR, "Added this item", "ValueSet.compose.include.filter"), r));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
session.markAdded(r);
}
}
return def;
@ -383,10 +381,10 @@ public class ValueSetComparer extends CanonicalResourceComparer {
def = !l.getDisplay().equals(r.getDisplay());
if (def) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" display changed from '"+l.getDisplay()+"' to '"+r.getDisplay()+"'", IssueSeverity.WARNING));
VersionComparisonAnnotation.markChanged(r.getDisplayElement(), session.getForVersion());
session.markChanged(r.getDisplayElement(), l.getDisplayElement());
}
} else if (l.hasDisplay()) {
VersionComparisonAnnotation.markDeleted(r, session.getForVersion(), "display", l.getDisplayElement());
session.markDeleted(r, "display", l.getDisplayElement());
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" display '"+l.getDisplay()+"' removed", IssueSeverity.WARNING));
sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), null, vmI(IssueSeverity.INFORMATION, "Display Removed", "ValueSet.compose.include.concept")));
if (ci != null) {
@ -395,7 +393,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
}
def = true;
} else if (r.hasDisplay()) {
VersionComparisonAnnotation.markAdded(r.getDisplayElement(), session.getForVersion());
session.markAdded(r.getDisplayElement());
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" display '"+r.getDisplay()+"' added", IssueSeverity.WARNING));
sm.getChildren().add(new StructuralMatch<Element>(null, r.getDisplayElement(), vmI(IssueSeverity.INFORMATION, "Display added", "ValueSet.compose.include.concept")));
if (ci != null) {

View File

@ -6,203 +6,87 @@ import java.util.List;
import java.util.Map;
import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.CanonicalResourceComparison;
import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.ChangeAnalysisState;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class VersionComparisonAnnotation {
public enum AnotationType {
NoChange, Added, Changed, ChildrenDeleted, Deleted;
NoChange, Added, Changed, Deleted;
}
public static final String USER_DATA_NAME = "version-annotation";
private AnotationType type;
private String original;
private Map<String, List<Base>> deletedChildren;
private String version;
private CanonicalResourceComparison<? extends CanonicalResource> comp;
private VersionComparisonAnnotation(AnotationType type, String version) {
public VersionComparisonAnnotation(AnotationType type) {
super();
this.type = type;
this.version = version;
}
public static void annotate(Base base, String version, CanonicalResourceComparison<? extends CanonicalResource> comp) {
if (version != null) {
VersionComparisonAnnotation vca = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME);
if (vca == null) {
vca = new VersionComparisonAnnotation(comp.noUpdates() ? AnotationType.NoChange : AnotationType.Changed, version);
base.setUserData(USER_DATA_NAME, vca);
}
vca.comp = comp;
}
public void added() {
assert type == AnotationType.NoChange;
type = AnotationType.Added;
}
public static void markAdded(Base focus, String version) {
if (version != null) {
focus.setUserData(USER_DATA_NAME, new VersionComparisonAnnotation(AnotationType.Added, version));
public void changed(Base orig) {
assert type == AnotationType.NoChange;
type = AnotationType.Changed;
if (orig != null && orig.isPrimitive() && orig.primitiveValue().length() < 120) { // arbitrary, but we don't a whack of markdown
this.original = orig.primitiveValue();
}
}
public static void markChanged(Base focus, String version) {
if (version != null) {
focus.setUserData(USER_DATA_NAME, new VersionComparisonAnnotation(AnotationType.Changed, version));
}
public void deleted() {
assert type == AnotationType.NoChange;
type = AnotationType.Deleted;
}
public static void markDeleted(Base parent, String version, String name, Base other) {
if (version != null && other != null) {
VersionComparisonAnnotation vca = null;
if (parent.hasUserData(USER_DATA_NAME)) {
vca = (VersionComparisonAnnotation) parent.getUserData(USER_DATA_NAME);
assert vca.type != AnotationType.Added;
} else {
vca = new VersionComparisonAnnotation(AnotationType.ChildrenDeleted, version);
parent.setUserData(USER_DATA_NAME, vca);
}
if (vca.deletedChildren == null) {
vca.deletedChildren = new HashMap<>();
}
if (!vca.deletedChildren.containsKey(name)) {
vca.deletedChildren.put(name, new ArrayList<>());
}
other.setUserData(USER_DATA_NAME, new VersionComparisonAnnotation(AnotationType.Deleted, version));
vca.deletedChildren.get(name).add(other);
public void deleted(String name, Base other) {
if (deletedChildren == null) {
deletedChildren = new HashMap<>();
}
if (!deletedChildren.containsKey(name)) {
deletedChildren.put(name, new ArrayList<>());
}
deletedChildren.get(name).add(other);
}
public void comp(CanonicalResourceComparison<? extends CanonicalResource> comp) {
assert this.comp == null;
// TODO Auto-generated method stub
if (!comp.noUpdates()) {
type = AnotationType.Changed;
}
this.comp = comp;
}
public static String getUserDataName() {
return USER_DATA_NAME;
}
public AnotationType getType() {
return type;
}
public void setType(AnotationType type) {
this.type = type;
}
public static XhtmlNode render(Base b, XhtmlNode x) {
if (b.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) b.getUserData(USER_DATA_NAME);
return self.render(x);
} else {
return x;
}
}
private XhtmlNode render(XhtmlNode x) {
switch (type) {
case Added:
XhtmlNode spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
XhtmlNode spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-add.png", "icon");
spanInner.tx(" Added:");
return spanOuter;
case Changed:
spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version);
spanInner.img("icon-change-edit.png", "icon");
spanInner.tx(" Changed:");
return spanOuter;
case Deleted:
spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+version);
spanInner.img("icon-change-remove.png", "icon");
spanInner.tx(" Removed:");
return spanOuter.strikethrough();
default:
return x;
}
public String getOriginal() {
return original;
}
public static XhtmlNode renderDiv(Base b, XhtmlNode x) {
if (b.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) b.getUserData(USER_DATA_NAME);
return self.renderDiv(x);
} else {
return x;
}
public Map<String, List<Base>> getDeletedChildren() {
return deletedChildren;
}
private XhtmlNode renderDiv(XhtmlNode x) {
switch (type) {
case Added:
XhtmlNode divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
XhtmlNode spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-add.png", "icon");
spanInner.tx(" Added:");
return divOuter;
case Changed:
divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version);
spanInner.img("icon-change-edit.png", "icon");
spanInner.tx(" Changed:");
return divOuter;
case Deleted:
divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+version);
spanInner.img("icon-change-remove.png", "icon");
spanInner.tx(" Removed:");
return divOuter.strikethrough();
default:
return x;
}
}
public static XhtmlNode renderRow(Base b, XhtmlNode tbl, XhtmlNode tr) {
if (b.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) b.getUserData(USER_DATA_NAME);
return self.renderRow(tbl, tr);
} else {
return tr.td();
}
}
private XhtmlNode renderRow(XhtmlNode tbl, XhtmlNode tr) {
switch (type) {
case Added:
if (tbl.isClass("grid")) {
tr.style("border: solid 1px #dddddd; margin: 2px; padding: 2px");
}
XhtmlNode td = tr.td();
XhtmlNode span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+version);
span.img("icon-change-add.png", "icon");
span.tx(" Added:");
XhtmlNode x = new XhtmlNode(NodeType.Element, "holder");
x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+version).tx(" ");
tr.styleCells(x);
return td;
case Changed:
td = tr.td();
span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been changed since "+version);
span.img("icon-change-edit.png", "icon");
span.tx(" Changed:");
return td;
case Deleted:
tr.style("text-decoration: line-through");
td = tr.td();
span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+version);
span.img("icon-change-remove.png", "icon");
span.tx(" Removed:");
x = new XhtmlNode(NodeType.Element, "holder");
x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px; text-decoration: none", "This row of content has been added since "+version).tx(" ");
tr.styleCells(x);
return td;
default:
return tr.td();
}
public CanonicalResourceComparison<? extends CanonicalResource> getComp() {
return comp;
}
public static boolean hasDeleted(Base base, String... names) {
boolean result = false;
if (base.hasUserData(USER_DATA_NAME)) {
@ -239,8 +123,10 @@ public class VersionComparisonAnnotation {
}
return result.isEmpty() ? null : result.get(0);
}
public static CanonicalResourceComparison<? extends CanonicalResource> artifactComparison(Base base) {
if (base.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME);
@ -250,39 +136,5 @@ public class VersionComparisonAnnotation {
}
}
public static void renderSummary(Base base, XhtmlNode x, String version, String... metadataFields) {
if (base.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME);
switch (self.type) {
case Added:
XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-add.png", "icon");
spanInner.tx(" Added");
return;
case Changed:
if (self.comp.noChangeOtherThanMetadata(metadataFields)) {
x.span("color: #eeeeee").tx("n/c");
return;
} else {
spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-edit.png", "icon");
spanInner.tx(" Changed");
}
return;
case Deleted:
spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-remove.png", "icon");
spanInner.tx(" Removed");
return;
default:
x.span("color: #eeeeee").tx("n/c");
return;
}
} else {
x.span("color: #eeeeee").tx("--");
}
}
}

View File

@ -113,6 +113,8 @@ import org.hl7.fhir.r5.renderers.OperationOutcomeRenderer;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.validation.VSCheckerException;
import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator;
@ -1008,7 +1010,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
BundleEntryComponent r = resp.getEntry().get(i);
if (r.getResource() instanceof Parameters) {
t.setResult(processValidationResult((Parameters) r.getResource()));
t.setResult(processValidationResult((Parameters) r.getResource(), vs == null ? null : vs.getUrl()));
if (txCache != null) {
txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT);
}
@ -1108,7 +1110,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
BundleEntryComponent r = resp.getEntry().get(i);
if (r.getResource() instanceof Parameters) {
t.setResult(processValidationResult((Parameters) r.getResource()));
t.setResult(processValidationResult((Parameters) r.getResource(), vsUrl));
if (txCache != null) {
txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT);
}
@ -1185,12 +1187,17 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
} catch (VSCheckerException e) {
if (e.isWarning()) {
localWarning = e.getMessage();
} else {
} else {
localError = e.getMessage();
}
if (e.getIssues() != null) {
issues.addAll(e.getIssues());
}
} catch (TerminologyServiceProtectionException e) {
OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR, e.getType());
iss.getDetails().setText(e.getMessage());
issues.add(iss);
return new ValidationResult(IssueSeverity.ERROR, e.getMessage(), e.getError(), issues);
} catch (Exception e) {
// e.printStackTrace();
localError = e.getMessage();
@ -1246,15 +1253,15 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
protected ValueSetExpander constructValueSetExpanderSimple() {
return new ValueSetExpander(this);
return new ValueSetExpander(this, new TerminologyOperationContext(this));
}
protected ValueSetValidator constructValueSetCheckerSimple( ValidationOptions options, ValueSet vs, ValidationContextCarrier ctxt) {
return new ValueSetValidator(options, vs, this, ctxt, expParameters, tcc.getTxcaps());
return new ValueSetValidator(this, new TerminologyOperationContext(this), options, vs, ctxt, expParameters, tcc.getTxcaps());
}
protected ValueSetValidator constructValueSetCheckerSimple( ValidationOptions options, ValueSet vs) {
return new ValueSetValidator(options, vs, this, expParameters, tcc.getTxcaps());
return new ValueSetValidator(this, new TerminologyOperationContext(this), options, vs, expParameters, tcc.getTxcaps());
}
protected Parameters constructParameters(ValueSet vs, boolean hierarchical) {
@ -1412,7 +1419,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
} else {
pOut = tcc.getClient().validateVS(pin);
}
return processValidationResult(pOut);
return processValidationResult(pOut, vs == null ? null : vs.getUrl());
}
protected void addServerValidationParameters(ValueSet vs, Parameters pin, ValidationOptions options) {
@ -1485,7 +1492,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return cache;
}
public ValidationResult processValidationResult(Parameters pOut) {
public ValidationResult processValidationResult(Parameters pOut, String vs) {
boolean ok = false;
String message = "No Message returned";
String display = null;
@ -1513,23 +1520,23 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED;
} else if (p.getName().equals("warning-withdrawn")) {
OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED);
iss.getDetails().setText(formatMessage(I18nConstants.MSG_WITHDRAWN, ((PrimitiveType<?>) p.getValue()).asStringValue()));
iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_WITHDRAWN : I18nConstants.MSG_WITHDRAWN_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));
issues.add(iss);
} else if (p.getName().equals("warning-deprecated")) {
OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED);
iss.getDetails().setText(formatMessage(I18nConstants.MSG_DEPRECATED, ((PrimitiveType<?>) p.getValue()).asStringValue()));
iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DEPRECATED : I18nConstants.MSG_DEPRECATED_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));
issues.add(iss);
} else if (p.getName().equals("warning-retired")) {
OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED);
iss.getDetails().setText(formatMessage(I18nConstants.MSG_RETIRED, ((PrimitiveType<?>) p.getValue()).asStringValue()));
iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_RETIRED : I18nConstants.MSG_RETIRED_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));
issues.add(iss);
} else if (p.getName().equals("warning-experimental")) {
OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
iss.getDetails().setText(formatMessage(I18nConstants.MSG_EXPERIMENTAL, ((PrimitiveType<?>) p.getValue()).asStringValue()));
iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_EXPERIMENTAL : I18nConstants.MSG_EXPERIMENTAL_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));
issues.add(iss);
} else if (p.getName().equals("warning-draft")) {
OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
iss.getDetails().setText(formatMessage(I18nConstants.MSG_DRAFT, ((PrimitiveType<?>) p.getValue()).asStringValue()));
iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DRAFT : I18nConstants.MSG_DRAFT_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));
issues.add(iss);
} else if (p.getName().equals("cause")) {
try {

View File

@ -594,7 +594,7 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
if (list != null) {
for (CanonicalResourceManager<T>.CachedCanonicalResource<T> t : list) {
possibleMatches = true;
if (pvlist == null || pvlist.contains(t.getPackageInfo().getVID())) {
if (pvlist == null || t.getPackageInfo() == null || pvlist.contains(t.getPackageInfo().getVID())) {
res.add(t.getResource());
}
}

View File

@ -37,6 +37,8 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class CodeSystemRenderer extends TerminologyRenderer {
private Boolean doMarkdown = null;
public CodeSystemRenderer(RenderingContext context) {
super(context);
}
@ -88,12 +90,12 @@ public class CodeSystemRenderer extends TerminologyRenderer {
tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Value", getContext().getLang()));
for (CodeSystemFilterComponent f : cs.getFilter()) {
tr = tbl.tr();
VersionComparisonAnnotation.render(f, tr.td()).tx(f.getCode());
VersionComparisonAnnotation.render(f.getDescriptionElement(), tr.td()).tx(f.getDescription());
renderStatus(f, tr.td()).tx(f.getCode());
renderStatus(f.getDescriptionElement(), tr.td()).tx(f.getDescription());
XhtmlNode td = tr.td();
for (Enumeration<org.hl7.fhir.r5.model.Enumerations.FilterOperator> t : f.getOperator())
VersionComparisonAnnotation.render(t, td).tx(t.asStringValue()+" ");
VersionComparisonAnnotation.render(f.getValueElement(), tr.td()).tx(f.getValue());
renderStatus(t, td).tx(t.asStringValue()+" ");
renderStatus(f.getValueElement(), tr.td()).tx(f.getValue());
}
}
}
@ -129,13 +131,13 @@ public class CodeSystemRenderer extends TerminologyRenderer {
if (hasRendered) {
tr.td().tx(ToolingExtensions.getPresentation(p, p.getCodeElement()));
}
VersionComparisonAnnotation.render(p, tr.td()).tx(p.getCode());
renderStatus(p, tr.td()).tx(p.getCode());
if (hasURI) {
VersionComparisonAnnotation.render(p.getUriElement(), tr.td()).tx(p.getUri());
renderStatus(p.getUriElement(), tr.td()).tx(p.getUri());
}
VersionComparisonAnnotation.render(p.getTypeElement(), tr.td()).tx(p.hasType() ? p.getType().toCode() : "");
renderStatus(p.getTypeElement(), tr.td()).tx(p.hasType() ? p.getType().toCode() : "");
if (hasDescription) {
VersionComparisonAnnotation.render(p.getDescriptionElement(), tr.td()).tx(p.getDescription());
renderStatus(p.getDescriptionElement(), tr.td()).tx(p.getDescription());
}
}
return true;
@ -174,7 +176,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
x.para().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Concepts", getContext().getLang()));
}
XhtmlNode p = x.para();
VersionComparisonAnnotation.render(cs.getUrlElement(), p.param("cs")).code().tx(cs.getUrl());
renderStatus(cs.getUrlElement(), p.param("cs")).code().tx(cs.getUrl());
makeCasedParam(p.param("cased"), cs, cs.getCaseSensitiveElement());
makeHierarchyParam(p.param("h"), cs, cs.getHierarchyMeaningElement());
@ -247,7 +249,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
private void makeHierarchyParam(XhtmlNode x, CodeSystem cs, Enumeration<CodeSystemHierarchyMeaning> hm) {
if (hm.hasValue()) {
String s = hm.getValue().getDisplay();
VersionComparisonAnnotation.render(hm, x).tx(" in a "+s+" heirarchy");
renderStatus(hm, x).tx(" in a "+s+" heirarchy");
} else if (VersionComparisonAnnotation.hasDeleted(cs, "hierarchyMeaning")) {
makeHierarchyParam(x, null, (Enumeration<CodeSystemHierarchyMeaning>) VersionComparisonAnnotation.getDeleted(cs, "hierarchyMeaning").get(0));
} else if (CodeSystemUtilities.hasHierarchy(cs)) {
@ -260,7 +262,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
private void makeCasedParam(XhtmlNode x, CodeSystem cs, BooleanType caseSensitiveElement) {
if (caseSensitiveElement.hasValue()) {
String s = caseSensitiveElement.getValue() == true? "case-sensitive" : "case-insensitive";
VersionComparisonAnnotation.render(caseSensitiveElement, x).tx(s);
renderStatus(caseSensitiveElement, x).tx(s);
} else if (VersionComparisonAnnotation.hasDeleted(cs, "caseSensitive")) {
makeCasedParam(x, null, (BooleanType) VersionComparisonAnnotation.getDeleted(cs, "caseSensitive").get(0));
} else {
@ -398,7 +400,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
tr.setAttribute("style", "background-color: #ffeeee");
}
XhtmlNode td = VersionComparisonAnnotation.renderRow(c, t, tr);
XhtmlNode td = renderStatusRow(c, t, tr);
if (hasHierarchy) {
td.addText(Integer.toString(level+1));
td = tr.td();
@ -425,9 +427,9 @@ public class CodeSystemRenderer extends TerminologyRenderer {
if (c != null &&c.hasDefinitionElement()) {
if (getContext().getLang() == null) {
if (hasMarkdownInDefinitions(cs)) {
addMarkdown(VersionComparisonAnnotation.renderDiv(c.getDefinitionElement(), td), c.getDefinition());
addMarkdown(renderStatusDiv(c.getDefinitionElement(), td), c.getDefinition());
} else {
VersionComparisonAnnotation.render(c.getDefinitionElement(), td).addText(c.getDefinition());
renderStatus(c.getDefinitionElement(), td).addText(c.getDefinition());
}
} else if (getContext().getLang().equals("*")) {
boolean sl = false;
@ -436,9 +438,9 @@ public class CodeSystemRenderer extends TerminologyRenderer {
sl = true;
td.addText((sl ? cs.getLanguage("en")+": " : ""));
if (hasMarkdownInDefinitions(cs))
addMarkdown(VersionComparisonAnnotation.renderDiv(c.getDefinitionElement(), td), c.getDefinition());
addMarkdown(renderStatusDiv(c.getDefinitionElement(), td), c.getDefinition());
else
VersionComparisonAnnotation.render(c.getDefinitionElement(), td).addText(c.getDefinition());
renderStatus(c.getDefinitionElement(), td).addText(c.getDefinition());
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
td.br();
@ -446,7 +448,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
}
}
} else if (getContext().getLang().equals(cs.getLanguage()) || (getContext().getLang().equals("en") && !cs.hasLanguage())) {
VersionComparisonAnnotation.render(c.getDefinitionElement(), td).addText(c.getDefinition());
renderStatus(c.getDefinitionElement(), td).addText(c.getDefinition());
} else {
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && cd.getLanguage().equals(getContext().getLang())) {
@ -616,14 +618,21 @@ public class CodeSystemRenderer extends TerminologyRenderer {
}
private boolean hasMarkdownInDefinitions(CodeSystem cs) {
return ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown");
if (doMarkdown == null) {
if (cs.hasExtension("http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown")) {
doMarkdown = ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown");
} else {
doMarkdown = CodeSystemUtilities.hasMarkdownInDefinitions(cs, context.getMarkdown());
}
}
return doMarkdown;
}
public void renderDisplayName(ConceptDefinitionComponent c, CodeSystem cs, XhtmlNode td) {
if (c.hasDisplayElement()) {
if (getContext().getLang() == null) {
VersionComparisonAnnotation.render(c.getDisplayElement(), td).addText(c.getDisplay());
renderStatus(c.getDisplayElement(), td).addText(c.getDisplay());
} else if (getContext().getLang().equals("*")) {
boolean sl = false;
for (ConceptDefinitionDesignationComponent cd : c.getDesignation())
@ -637,7 +646,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
}
}
} else if (getContext().getLang().equals(cs.getLanguage()) || (getContext().getLang().equals("en") && !cs.hasLanguage())) {
VersionComparisonAnnotation.render(c.getDisplayElement(), td).addText(c.getDisplay());
renderStatus(c.getDisplayElement(), td).addText(c.getDisplay());
} else {
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(getContext().getLang())) {

View File

@ -1,6 +1,8 @@
package org.hl7.fhir.r5.renderers;
import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType;
@ -11,6 +13,7 @@ import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
/**
@ -84,4 +87,144 @@ public class Renderer extends TranslatingUtilities {
}
}
protected XhtmlNode renderStatus(Base b, XhtmlNode x) {
if (b == null || context.getChangeVersion() == null) {
return x;
}
VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME);
if (vca == null) {
return x;
}
switch (vca.getType()) {
case Added:
XhtmlNode spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
XhtmlNode spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+context.getChangeVersion());
spanInner.img("icon-change-add.png", "icon");
spanInner.tx(" Added:");
return spanOuter;
case Changed:
spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+vca.getOriginal()+"')" : ""));
spanInner.img("icon-change-edit.png", "icon");
spanInner.tx(" Changed:");
return spanOuter;
case Deleted:
spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion());
spanInner.img("icon-change-remove.png", "icon");
spanInner.tx(" Removed:");
return spanOuter.strikethrough();
default:
return x;
}
}
protected XhtmlNode renderStatusDiv(Base b, XhtmlNode x) {
if (b == null || context.getChangeVersion() == null) {
return x;
}
VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME);
if (vca == null) {
return x;
}
switch (vca.getType()) {
case Added:
XhtmlNode divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
XhtmlNode spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+context.getChangeVersion());
spanInner.img("icon-change-add.png", "icon");
spanInner.tx(" Added:");
return divOuter;
case Changed:
divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+(vca.getOriginal())+"')" : ""));
spanInner.img("icon-change-edit.png", "icon");
spanInner.tx(" Changed:");
return divOuter;
case Deleted:
divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion());
spanInner.img("icon-change-remove.png", "icon");
spanInner.tx(" Removed:");
return divOuter.strikethrough();
default:
return x;
}
}
protected XhtmlNode renderStatusRow(Base b, XhtmlNode tbl, XhtmlNode tr) {
if (b == null || context.getChangeVersion() == null) {
return tr.td();
}
VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME);
if (vca == null) {
return tr.td();
}
switch (vca.getType()) {
case Added:
if (tbl.isClass("grid")) {
tr.style("border: solid 1px #dddddd; margin: 2px; padding: 2px");
}
XhtmlNode td = tr.td();
XhtmlNode span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+context.getChangeVersion());
span.img("icon-change-add.png", "icon");
span.tx(" Added:");
XhtmlNode x = new XhtmlNode(NodeType.Element, "holder");
x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+context.getChangeVersion()).tx(" ");
tr.styleCells(x);
return td;
case Changed:
td = tr.td();
span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been changed since"+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+vca.getOriginal()+"')" : ""));
span.img("icon-change-edit.png", "icon");
span.tx(" Changed:");
return td;
case Deleted:
tr.style("text-decoration: line-through");
td = tr.td();
span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion());
span.img("icon-change-remove.png", "icon");
span.tx(" Removed:");
x = new XhtmlNode(NodeType.Element, "holder");
x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px; text-decoration: none", "This row of content has been added since "+context.getChangeVersion()).tx(" ");
tr.styleCells(x);
return td;
default:
return tr.td();
}
}
public static void renderStatusSummary(Base base, XhtmlNode x, String version, String... metadataFields) {
if (base.hasUserData(VersionComparisonAnnotation.USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(VersionComparisonAnnotation.USER_DATA_NAME);
switch (self.getType()) {
case Added:
XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-add.png", "icon");
spanInner.tx(" Added");
return;
case Changed:
if (self.getComp().noChangeOtherThanMetadata(metadataFields)) {
x.span("color: #eeeeee").tx("n/c");
return;
} else {
spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version+(self.getOriginal() != null ? " (was '"+(self.getOriginal())+"')" : ""));
spanInner.img("icon-change-edit.png", "icon");
spanInner.tx(" Changed");
}
return;
case Deleted:
spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-remove.png", "icon");
spanInner.tx(" Removed");
return;
default:
x.span("color: #eeeeee").tx("n/c");
return;
}
} else {
x.span("color: #eeeeee").tx("--");
}
}
}

View File

@ -7,6 +7,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Stack;
import java.util.Map;
import java.util.Set;
@ -54,6 +55,7 @@ import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.MarkdownType;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Quantity;
import org.hl7.fhir.r5.model.Resource;
@ -79,6 +81,7 @@ import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
@ -172,10 +175,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
private abstract class ItemWithStatus {
ListItemStatus status = ListItemStatus.New; // new, unchanged, removed
protected abstract void renderDetails(XhtmlNode f);
protected abstract void renderDetails(XhtmlNode f) throws IOException;
protected abstract boolean matches(ItemWithStatus other);
public void render(XhtmlNode x) {
public void render(XhtmlNode x) throws IOException {
XhtmlNode f = x;
if (status == ListItemStatus.Unchanged) {
f = unchanged(f);
@ -247,14 +250,25 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
public void renderDetails(XhtmlNode f) {
f = renderStatus(value, f);
f.b().attribute("title", "Formal Invariant Identifier").tx(value.getKey());
f.tx(": ");
f.tx(value.getHuman());
if (value.hasHuman()) {
renderStatus(value.getHumanElement(), f).tx(value.getHuman());
} else if (VersionComparisonAnnotation.hasDeleted(value, "human")) {
Base b =VersionComparisonAnnotation.getDeletedItem(value, "human");
renderStatus(b, f).tx(b.primitiveValue());
}
f.tx(" (");
if (status == ListItemStatus.New) {
f.code().tx(value.getExpression());
if (value.hasExpression()) {
renderStatus(value.getExpressionElement(), f).code().tx(value.getExpression());
} else if (VersionComparisonAnnotation.hasDeleted(value, "expression")) {
Base b = VersionComparisonAnnotation.getDeletedItem(value, "expression");
renderStatus(b, f).code().tx(b.primitiveValue());
}
} else {
f.tx(value.getExpression());
renderStatus(value.getExpressionElement(), f).tx(value.getExpression());
}
f.tx(")");
}
@ -293,6 +307,27 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
f.tx(value.asStringValue());
}
}
private class DataValueWithStatus extends ItemWithStatus {
DataType value;
protected DataValueWithStatus(DataType value) {
this.value = value;
}
protected boolean matches(ItemWithStatus other) {
return ((ValueWithStatus) other).value.equalsDeep(value);
}
public void renderDetails(XhtmlNode f) throws IOException {
if (value.hasUserData("render.link")) {
f = f.ah(value.getUserString("render.link"));
}
f.tx(summarize(value));
}
}
@ -303,6 +338,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
public StructureDefinitionRenderer(RenderingContext context) {
super(context);
hostMd = new InternalMarkdownProcessor();
corePath = context.getContext().getSpecUrl();
}
public StructureDefinitionRenderer(RenderingContext context, ResourceContext rcontext) {
@ -3060,8 +3096,6 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return null;
}
private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
int i = ed.getSnapshot().getElement().indexOf(c) + 1;
while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
@ -3083,6 +3117,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
generateAnchors(stack, allAnchors);
checkInScope(stack, excluded);
}
Stack<ElementDefinition> dstack = new Stack<>();
for (ElementDefinition ec : elements) {
if ((incProfiledOut || !"0".equals(ec.getMax())) && !excluded.contains(ec)) {
ElementDefinition compareElement = null;
@ -3094,7 +3129,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
List<String> anchors = makeAnchors(ec, anchorPrefix);
String title = ec.getId();
XhtmlNode tr = t.tr();
XhtmlNode sp = VersionComparisonAnnotation.render(ec, tr.td("structure").colspan(2).spanClss("self-link-parent"));
XhtmlNode sp = renderStatus(ec, tr.td("structure").colspan(2).spanClss("self-link-parent"));
for (String s : anchors) {
sp.an(s).tx(" ");
}
@ -3116,6 +3151,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
// generateElementInner(b, extDefn, extDefn.getSnapshot().getElement().get(0), valueDefn == null ? 2 : 3, valueDefn);
}
} else {
while (!dstack.isEmpty() && !isParent(dstack.peek(), ec)) {
finish(t, sd, dstack.pop(), mode);
}
dstack.push(ec);
generateElementInner(t, sd, ec, mode, null, compareElement, null, false);
if (ec.hasSlicing()) {
generateSlicing(t, sd, ec, ec.getSlicing(), compareElement, mode, false);
@ -3125,20 +3164,26 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
t.tx("\r\n");
i++;
}
for (Base b : VersionComparisonAnnotation.getDeleted(sd, "element")) {
while (!dstack.isEmpty()) {
finish(t, sd, dstack.pop(), mode);
}
finish(t, sd, null, mode);
}
private void finish(XhtmlNode t, StructureDefinition sd, ElementDefinition ed, int mode) throws FHIRException, IOException {
for (Base b : VersionComparisonAnnotation.getDeleted(ed == null ? sd : ed, "element")) {
ElementDefinition ec = (ElementDefinition) b;
String title = ec.getId();
XhtmlNode tr = t.tr();
XhtmlNode sp = VersionComparisonAnnotation.render(ec, tr.td("structure").colspan(2).spanClss("self-link-parent"));
sp.span("color: grey", null).tx(Integer.toString(i++));
XhtmlNode sp = renderStatus(ec, tr.td("structure").colspan(2).spanClss("self-link-parent"));
sp.span("color: grey", null).tx("--");
sp.b().tx(". "+title);
generateElementInner(t, sd, ec, mode, null, null, null, true);
if (ec.hasSlicing()) {
generateSlicing(t, sd, ec, ec.getSlicing(), null, mode, true);
}
t.tx("\r\n");
}
}
}
@ -3309,8 +3354,15 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (compare == null || mode == GEN_MODE_DIFF) {
if (md.hasValue()) {
String xhtml = hostMd.processMarkdown(location, md);
if (Utilities.noString(xhtml)) {
return null;
}
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
VersionComparisonAnnotation.renderDiv(md, x).add(new XhtmlParser().parseFragment(xhtml));
try {
renderStatusDiv(md, x).add(new XhtmlParser().parseFragment(xhtml));
} catch (Exception e) {
x.span("color: maroon").tx(e.getLocalizedMessage());
}
return x;
} else {
return null;
@ -3369,10 +3421,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
if (mode != GEN_MODE_KEY) {
if (newStr != null) {
VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr);
renderStatus(source, x).ah(nLink).tx(newStr);
} else if (VersionComparisonAnnotation.hasDeleted(parent, name)) {
PrimitiveType p = (PrimitiveType) VersionComparisonAnnotation.getDeletedItem(parent, name);
VersionComparisonAnnotation.render(p, x).tx(p.primitiveValue());
renderStatus(p, x).tx(p.primitiveValue());
} else {
return null;
}
@ -3380,7 +3432,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (newStr==null || newStr.isEmpty()) {
return null;
} else {
VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr);
renderStatus(source, x).ah(nLink).tx(newStr);
}
} else if (oldStr!=null && !oldStr.isEmpty() && (newStr==null || newStr.isEmpty())) {
if (mode == GEN_MODE_DIFF) {
@ -3396,10 +3448,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
} else if (newStr.startsWith(oldStr)) {
unchanged(x).ah(oLink).tx(oldStr);
VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr.substring(oldStr.length()));
renderStatus(source, x).ah(nLink).tx(newStr.substring(oldStr.length()));
} else {
// TODO: improve comparision in this fall-through case, by looking for matches in sub-paragraphs?
VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr);
renderStatus(source, x).ah(nLink).tx(newStr);
removed(x).ah(oLink).tx(oldStr);
}
return x;
@ -3436,17 +3488,20 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
boolean slicedExtension = d.hasSliceName() && (d.getPath().endsWith(".extension") || d.getPath().endsWith(".modifierExtension"));
// int slicedExtensionMode = (mode == GEN_MODE_KEY) && slicedExtension ? GEN_MODE_SNAP : mode; // see ProfileUtilities.checkExtensionDoco / Task 3970
if (d.hasSliceName()) {
tableRow(tbl, "SliceName", "profiling.html#slicing", strikethrough).tx(d.getSliceName());
tableRow(tbl, "Slice Name", "profiling.html#slicing", strikethrough, compareString(d.getSliceName(), d.getSliceNameElement(), null, (compare != null ? compare.getSliceName() : null), d, null, "sliceName", mode));
tableRow(tbl, "Slice Constraining", "profiling.html#slicing", strikethrough, compareString(encodeValue(d.getSliceIsConstrainingElement()), d.getSliceIsConstrainingElement(), null, (compare != null ? encodeValue(compare.getSliceIsConstrainingElement()) : null), d, null, "sliceName", mode));
}
tableRow(tbl, "Definition", null, strikethrough, compareMarkdown(sd.getName(), d.getDefinitionElement(), (compare==null) || slicedExtension ? null : compare.getDefinitionElement(), mode));
tableRow(tbl, "Short", null, strikethrough, compareString(d.hasShort() ? d.getShort() : null, d.getShortElement(), null, "short", d, compare!= null && compare.hasShortElement() ? compare.getShort() : null, null, mode));
tableRow(tbl, "Comments", null, strikethrough, compareMarkdown(sd.getName(), d.getCommentElement(), (compare==null) || slicedExtension ? null : compare.getCommentElement(), mode));
tableRow(tbl, "Note", null, strikethrough, businessIdWarning(sd.getName(), tail(d.getPath())));
tableRow(tbl, "Control", "conformance-rules.html#conformance", strikethrough, describeCardinality(d, compare, mode));
tableRow(tbl, "Binding", "terminologies.html", strikethrough, describeBinding(sd, d, d.getPath(), compare, mode));
if (d.hasContentReference()) {
tableRow(tbl, "Type", null, strikethrough, "See " + d.getContentReference().substring(1));
} else {
tableRow(tbl, "Type", "datatypes.html", strikethrough, describeTypes(d.getType(), false, compare, mode, value, compareValue, sd));
tableRow(tbl, "Type", "datatypes.html", strikethrough, describeTypes(d.getType(), false, d, compare, mode, value, compareValue, sd));
}
if (d.hasExtension(ToolingExtensions.EXT_DEF_TYPE)) {
tableRow(tbl, "Default Type", "datatypes.html", strikethrough, ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_DEF_TYPE));
@ -3457,7 +3512,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (d.getPath().endsWith("[x]") && !d.prohibited()) {
tableRow(tbl, "[x] Note", null, strikethrough).ahWithText("See ", spec("formats.html#choice"), null, "Choice of Data Types", " for further information about how to use [x]");
}
tableRow(tbl, "Is Modifier", "conformance-rules.html#ismodifier", strikethrough, displayBoolean(d.getIsModifier(), d.getIsModifierElement(), "isModifier", d, null, mode));
tableRow(tbl, "Is Modifier", "conformance-rules.html#ismodifier", strikethrough, presentModifier(d, mode, compare));
if (d.getMustHaveValue()) {
tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", strikethrough, "This primitive type must have a value (the value must be present, and cannot be replaced by an extension)");
} else if (d.hasValueAlternatives()) {
@ -3471,7 +3526,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
tableRow(tbl, "Must Support", "conformance-rules.html#mustSupport", strikethrough, displayBoolean(d.getMustSupport(), d.getMustSupportElement(), "mustSupport", d, compare==null ? null : compare.getMustSupportElement(), mode));
if (d.getMustSupport()) {
if (hasMustSupportTypes(d.getType())) {
tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, describeTypes(d.getType(), true, compare, mode, null, null, sd));
tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, describeTypes(d.getType(), true, d, compare, mode, null, null, sd));
} else if (hasChoices(d.getType())) {
tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, "No must-support rules about the choice of types/profiles");
}
@ -3552,19 +3607,37 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
tableRow(tbl, "Summary", "search.html#summary", strikethrough, Boolean.toString(d.getIsSummary()));
}
tableRow(tbl, "Requirements", null, strikethrough, compareMarkdown(sd.getName(), d.getRequirementsElement(), (compare==null) || slicedExtension ? null : compare.getRequirementsElement(), mode));
tableRow(tbl, "Label", null, strikethrough, compareString(d.getLabel(), d.getLabelElement(), null, "label", d, (compare != null ? compare.getLabel() : null), null, mode));
tableRow(tbl, "Alternate Names", null, strikethrough, compareSimpleTypeLists(d.getAlias(), ((compare==null) || slicedExtension ? null : compare.getAlias()), mode));
tableRow(tbl, "Comments", null, strikethrough, compareMarkdown(sd.getName(), d.getCommentElement(), (compare==null) || slicedExtension ? null : compare.getCommentElement(), mode));
tableRow(tbl, "Definitional Codes", null, strikethrough, compareDataTypeLists(d.getCode(), ((compare==null) || slicedExtension ? null : compare.getCode()), mode));
tableRow(tbl, "Min Value", null, strikethrough, compareString(d.hasMinValue() ? encodeValue(d.getMinValue()) : null, d.getMinValue(), null, "minValue", d, compare!= null && compare.hasMinValue() ? encodeValue(compare.getMinValue()) : null, null, mode));
tableRow(tbl, "Max Value", null, strikethrough, compareString(d.hasMaxValue() ? encodeValue(d.getMaxValue()) : null, d.getMaxValue(), null, "maxValue", d, compare!= null && compare.hasMaxValue() ? encodeValue(compare.getMaxValue()) : null, null, mode));
tableRow(tbl, "Max Length", null, strikethrough, compareString(d.hasMaxLength() ? toStr(d.getMaxLength()) : null, d.getMaxLengthElement(), null, "maxLength", d, compare!= null && compare.hasMaxLengthElement() ? toStr(compare.getMaxLength()) : null, null, mode));
tableRow(tbl, "Value Required", null, strikethrough, compareString(encodeValue(d.getMustHaveValueElement()), d.getMustHaveValueElement(), null, (compare != null ? encodeValue(compare.getMustHaveValueElement()) : null), d, null, "mustHaveValueElement", mode));
tableRow(tbl, "Value Alternatives", null, strikethrough, compareSimpleTypeLists(d.getValueAlternatives(), ((compare==null) || slicedExtension ? null : compare.getValueAlternatives()), mode));
tableRow(tbl, "Default Value", null, strikethrough, encodeValue(d.getDefaultValue(), "defaultValue", d, compare==null ? null : compare.getDefaultValue(), mode));
tableRow(tbl, "Meaning if Missing", null, strikethrough, d.getMeaningWhenMissing());
tableRow(tbl, "Fixed Value", null, strikethrough, encodeValue(d.getFixed(), "fixed", d, compare==null ? null : compare.getFixed(), mode));
tableRow(tbl, "Pattern Value", null, strikethrough, encodeValue(d.getPattern(), "pattern", d, compare==null ? null : compare.getPattern(), mode));
tableRow(tbl, "Example", null, strikethrough, encodeValues(d.getExample()));
tableRow(tbl, "Invariants", null, strikethrough, invariants(d.getConstraint(), compare==null ? null : compare.getConstraint(), mode));
tableRow(tbl, "Invariants", null, strikethrough, invariants(d.getConstraint(), compare==null ? null : compare.getConstraint(), d, mode));
tableRow(tbl, "LOINC Code", null, strikethrough, getMapping(sd, d, LOINC_MAPPING, compare, mode));
tableRow(tbl, "SNOMED-CT Code", null, strikethrough, getMapping(sd, d, SNOMED_MAPPING, compare, mode));
tbl.tx("\r\n");
}
private XhtmlNode presentModifier(ElementDefinition d, int mode, ElementDefinition compare) throws FHIRException, IOException {
XhtmlNode x1 = compareString(encodeValue(d.getIsModifierElement()), d.getIsModifierElement(), null, "isModifier", d, compare == null ? null : encodeValue(compare.getIsModifierElement()), null, mode);
if (x1 != null) {
XhtmlNode x2 = compareString(encodeValue(d.getIsModifierReasonElement()), d.getIsModifierReasonElement(), null, "isModifierReason", d, compare == null ? null : encodeValue(compare.getIsModifierReasonElement()), null, mode);
if (x2 != null) {
x1.tx(" because ");
x1.copyAllContent(x2);
}
}
return x1;
}
private String spec(String name) {
return Utilities.pathURL(VersionUtilities.getSpecUrl(context.getWorker().getVersion()) , name);
}
@ -3817,6 +3890,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
x.tx(", and defines no discriminators to differentiate the slices");
}
tableRow(tbl, "Slicing", "profiling.html#slicing", strikethrough, x);
tbl.tx("\r\n");
}
private XhtmlNode tableRow(XhtmlNode x, String name, String defRef, boolean strikethrough) throws IOException {
@ -3896,18 +3970,18 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return null;
}
private XhtmlNode describeCardinality(ElementDefinition d, ElementDefinition compare, int mode) {
private XhtmlNode describeCardinality(ElementDefinition d, ElementDefinition compare, int mode) throws IOException {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
if (compare==null || mode==GEN_MODE_DIFF) {
if (!d.hasMax() && !d.hasMin())
return null;
else if (d.getMax() == null) {
VersionComparisonAnnotation.render(d.getMinElement(), x).tx(toStr(d.getMin()));
renderStatus(d.getMinElement(), x).tx(toStr(d.getMin()));
x.tx("..?");
} else {
VersionComparisonAnnotation.render(d.getMinElement(), x).tx(toStr(d.getMin()));
renderStatus(d.getMinElement(), x).tx(toStr(d.getMin()));
x.tx( "..");
VersionComparisonAnnotation.render(d.getMaxElement(), x).tx( d.getMax());
renderStatus(d.getMaxElement(), x).tx( d.getMax());
}
} else {
if (!(mode==GEN_MODE_DIFF && (d.getMin()==compare.getMin() || d.getMin()==0))) {
@ -3936,19 +4010,21 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return false;
}
private XhtmlNode describeTypes(List<TypeRefComponent> types, boolean mustSupportOnly, ElementDefinition compare, int mode, ElementDefinition value, ElementDefinition compareValue, StructureDefinition sd) throws FHIRException {
private XhtmlNode describeTypes(List<TypeRefComponent> types, boolean mustSupportOnly, ElementDefinition ed, ElementDefinition compare, int mode, ElementDefinition value, ElementDefinition compareValue, StructureDefinition sd) throws FHIRException, IOException {
if (types.isEmpty())
return null;
List<TypeRefComponent> compareTypes = compare==null ? new ArrayList<>() : compare.getType();
XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
if ((!mustSupportOnly && types.size() == 1 && compareTypes.size() <=1) || (mustSupportOnly && mustSupportCount(types) == 1)) {
if ((!mustSupportOnly && types.size() == 1 && compareTypes.size() <=1 && (mode != GEN_MODE_DIFF || !VersionComparisonAnnotation.hasDeleted(ed, "type"))) || (mustSupportOnly && mustSupportCount(types) == 1)) {
if (!mustSupportOnly || isMustSupport(types.get(0))) {
describeType(ret, types.get(0), mustSupportOnly, compareTypes.size()==0 ? null : compareTypes.get(0), mode, sd);
}
} else {
boolean first = true;
ret.tx("Choice of: ");
if (types.size() > 1) {
ret.tx("Choice of: ");
}
Map<String,TypeRefComponent> map = new HashMap<String, TypeRefComponent>();
for (TypeRefComponent t : compareTypes) {
map.put(t.getCode(), t);
@ -3970,6 +4046,13 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
ret.tx(", ");
describeType(removed(ret), t, mustSupportOnly, null, mode, sd);
}
if (mode == GEN_MODE_DIFF) {
for (Base b : VersionComparisonAnnotation.getDeleted(ed, "type")) {
TypeRefComponent t = (TypeRefComponent) b;
ret.tx(", ");
describeType(ret, t, false, null, mode, sd);
}
}
}
if (value != null) {
XhtmlNode xt = processSecondary(mode, value, compareValue, mode, sd);
@ -3980,7 +4063,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return ret;
}
private XhtmlNode processSecondary(int mode, ElementDefinition value, ElementDefinition compareValue, int compMode, StructureDefinition sd) throws FHIRException {
private XhtmlNode processSecondary(int mode, ElementDefinition value, ElementDefinition compareValue, int compMode, StructureDefinition sd) throws FHIRException, IOException {
switch (mode) {
case 1:
return null;
@ -3991,7 +4074,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
case 3:
x = new XhtmlNode(NodeType.Element, "div");
x.tx(" (Extension Type: ");
x.copyAllContent(describeTypes(value.getType(), false, compareValue, compMode, null, null, sd));
x.copyAllContent(describeTypes(value.getType(), false, value, compareValue, compMode, null, null, sd));
x.tx(")");
return x;
default:
@ -4011,7 +4094,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
private void describeType(XhtmlNode x, TypeRefComponent t, boolean mustSupportOnly, TypeRefComponent compare, int mode, StructureDefinition sd) throws FHIRException {
private void describeType(XhtmlNode x, TypeRefComponent t, boolean mustSupportOnly, TypeRefComponent compare, int mode, StructureDefinition sd) throws FHIRException, IOException {
if (t.getWorkingCode() == null) {
return;
}
@ -4021,9 +4104,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
boolean ts = false;
if (t.getWorkingCode().startsWith("xs:")) {
ts = compareString(x, t.getWorkingCode(), t.getCodeElement(), null, "code", t, compare==null ? null : compare.getWorkingCode(), null, mode);
ts = compareString(x, t.getWorkingCode(), t, null, "code", t, compare==null ? null : compare.getWorkingCode(), null, mode);
} else {
ts = compareString(x, t.getWorkingCode(), t.getCodeElement(), getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode);
ts = compareString(x, t.getWorkingCode(), t, getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode);
}
if ((!mustSupportOnly && (t.hasProfile() || (compare!=null && compare.hasProfile()))) || isMustSupport(t.getProfile())) {
@ -4140,7 +4223,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
private XhtmlNode invariants(List<ElementDefinitionConstraintComponent> originalList, List<ElementDefinitionConstraintComponent> compareList, int mode) {
private XhtmlNode invariants(List<ElementDefinitionConstraintComponent> originalList, List<ElementDefinitionConstraintComponent> compareList, ElementDefinition parent, int mode) throws IOException {
StatusList<InvariantWithStatus> list = new StatusList<>();
for (ElementDefinitionConstraintComponent v : originalList) {
if (!v.isEmpty()) {
@ -4161,6 +4244,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (first) first = false; else x.br();
t.render(x);
}
for (Base b : VersionComparisonAnnotation.getDeleted(parent, "invariant")) {
if (first) first = false; else x.br();
InvariantWithStatus ts = new InvariantWithStatus((ElementDefinitionConstraintComponent) b);
ts.render(x);
}
return x;
}
@ -4172,31 +4260,23 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
ElementDefinitionBindingComponent compBinding = compare == null ? null : compare.getBinding();
XhtmlNode bindingDesc = null;
if (binding.hasDescription()) {
StringType newBinding = PublicationHacker.fixBindingDescriptions(context.getContext(), binding.getDescriptionElement());
MarkdownType newBinding = PublicationHacker.fixBindingDescriptions(context.getContext(), binding.getDescriptionElement());
if (mode == GEN_MODE_SNAP || mode == GEN_MODE_MS) {
bindingDesc = new XhtmlNode(NodeType.Element, "div");
bindingDesc.add(new XhtmlParser().parseFragment(hostMd.processMarkdown("Binding.description", newBinding)));
} else {
StringType oldBinding = compBinding != null && compBinding.hasDescription() ? PublicationHacker.fixBindingDescriptions(context.getContext(), compBinding.getDescriptionElement()) : null;
bindingDesc = compareMarkdown("Binding.description", newBinding, oldBinding, mode);
}
}
if (!binding.hasValueSet())
if (!binding.hasValueSet()) {
return bindingDesc;
}
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
var nsp = x.span();
renderBinding(nsp, binding, path, sd);
if (compBinding!=null ) {
var osp = x.span();
renderBinding(osp, compBinding, path, sd);
if (osp.allText().equals(nsp.allText())) {
nsp.style(unchangedStyle());
x.remove(osp);
} else {
osp.style(removedStyle());
}
}
renderBinding(nsp, binding, compBinding, path, sd, mode);
if (bindingDesc != null) {
if (isSimpleContent(bindingDesc)) {
x.tx(": ");
@ -4232,16 +4312,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return bindingDesc.getChildNodes().size() == 1 && bindingDesc.getChildNodes().get(0).isPara();
}
private void renderBinding(XhtmlNode span, ElementDefinitionBindingComponent binding, String path, StructureDefinition sd) {
private void renderBinding(XhtmlNode span, ElementDefinitionBindingComponent binding, ElementDefinitionBindingComponent compare, String path, StructureDefinition sd, int mode) {
compareString(span, conf(binding), binding.getStrengthElement(), null, "strength", binding, compare == null ? null : conf(compare), null, mode);
span.tx(" ");
BindingResolution br = context.getPkp().resolveBinding(sd, binding, path);
span.tx(conf(binding));
if (br.url == null) {
span.code().tx(br.display);
} else {
span.ah(br.url).tx(br.display);
}
span.tx(confTail(binding));
compareString(span, br.display, binding.getValueSetElement(), br.url, "valueSet", binding, compare == null ? null : compare.getValueSet(), null, mode);
}
private String stripPara(String s) {
@ -4254,13 +4329,6 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return s;
}
private String confTail(ElementDefinitionBindingComponent def) {
if (def.getStrength() == BindingStrength.EXTENSIBLE)
return "; other codes may be used where these codes are not suitable";
else
return "";
}
private String conf(ElementDefinitionBindingComponent def) {
if (def.getStrength() == null) {
return "For codes, see ";
@ -4271,7 +4339,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
case PREFERRED:
return "The codes SHOULD be taken from ";
case EXTENSIBLE:
return "The codes SHALL be taken from ";
return "Unless not suitable, these codes SHALL be taken from ";
case REQUIRED:
return "The codes SHALL be taken from ";
default:
@ -4348,12 +4416,12 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return compareString(Utilities.escapeXml(newMap), null, null, "mapping", d, Utilities.escapeXml(oldMap), null, mode);
}
private XhtmlNode compareSimpleTypeLists(List<? extends PrimitiveType> original, List<? extends PrimitiveType> compare, int mode) {
private XhtmlNode compareSimpleTypeLists(List<? extends PrimitiveType> original, List<? extends PrimitiveType> compare, int mode) throws IOException {
return compareSimpleTypeLists(original, compare, mode, ", ");
}
private XhtmlNode compareSimpleTypeLists(List<? extends PrimitiveType> originalList, List<? extends PrimitiveType> compareList, int mode, String separator) {
private XhtmlNode compareSimpleTypeLists(List<? extends PrimitiveType> originalList, List<? extends PrimitiveType> compareList, int mode, String separator) throws IOException {
StatusList<ValueWithStatus> list = new StatusList<>();
for (PrimitiveType v : originalList) {
if (!v.isEmpty()) {
@ -4378,6 +4446,37 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
private XhtmlNode compareDataTypeLists(List<? extends DataType> original, List<? extends DataType> compare, int mode) throws IOException {
return compareDataTypeLists(original, compare, mode, ", ");
}
private XhtmlNode compareDataTypeLists(List<? extends DataType> originalList, List<? extends DataType> compareList, int mode, String separator) throws IOException {
StatusList<DataValueWithStatus> list = new StatusList<>();
for (DataType v : originalList) {
if (!v.isEmpty()) {
list.add(new DataValueWithStatus(v));
}
}
if (compareList != null && mode != GEN_MODE_DIFF) {
for (DataType v : compareList) {
list.merge(new DataValueWithStatus(v));
}
}
if (list.size() == 0) {
return null;
}
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
boolean first = true;
for (DataValueWithStatus t : list) {
if (first) first = false; else x.tx(separator);
t.render(x);
}
return x;
}
private String summarise(CodeableConcept cc) throws FHIRException {
if (cc.getCoding().size() == 1 && cc.getText() == null) {
return summarise(cc.getCoding().get(0));

View File

@ -481,7 +481,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
}
private void expRef(XhtmlNode x, String u, String v, Resource source) {
String t = u.substring(0, u.indexOf("|"));
String t = u.contains("|") ? u.substring(0, u.indexOf("|")) : u;
u = u.substring(u.indexOf("|")+1);
// TODO Auto-generated method stub
if (u.equals("http://snomed.info/sct")) {
@ -1146,7 +1146,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
boolean hasExtensions = false;
XhtmlNode li;
li = ul.li();
li = VersionComparisonAnnotation.render(inc, li);
li = renderStatus(inc, li);
Map<String, ConceptDefinitionComponent> definitions = new HashMap<>();
@ -1199,7 +1199,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
li.tx(", ");
}
}
XhtmlNode wli = VersionComparisonAnnotation.render(f, li);
XhtmlNode wli = renderStatus(f, li);
if (f.getOp() == FilterOperator.EXISTS) {
if (f.getValue().equals("true")) {
wli.tx(f.getProperty()+" exists");
@ -1239,7 +1239,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
first = false;
else
li.tx(", ");
XhtmlNode wli = VersionComparisonAnnotation.render(vs, li);
XhtmlNode wli = renderStatus(vs, li);
AddVsRef(vs.asStringValue(), wli, vsRes);
}
}
@ -1256,13 +1256,13 @@ public class ValueSetRenderer extends TerminologyRenderer {
first = false;
else
li.tx(", ");
XhtmlNode wli = VersionComparisonAnnotation.render(vs, li);
XhtmlNode wli = renderStatus(vs, li);
AddVsRef(vs.asStringValue(), wli, vsRes);
}
} else {
XhtmlNode xul = li.ul();
for (UriType vs : inc.getValueSet()) {
XhtmlNode wli = VersionComparisonAnnotation.render(vs, xul.li());
XhtmlNode wli = renderStatus(vs, xul.li());
AddVsRef(vs.asStringValue(), wli, vsRes);
}
@ -1275,16 +1275,16 @@ public class ValueSetRenderer extends TerminologyRenderer {
List<UsedConceptMap> maps, Map<String, String> designations, Map<String, ConceptDefinitionComponent> definitions,
XhtmlNode t, boolean hasComments, boolean hasDefinition, ConceptReferenceComponent c) {
XhtmlNode tr = t.tr();
XhtmlNode td = VersionComparisonAnnotation.renderRow(c, t, tr);
XhtmlNode td = renderStatusRow(c, t, tr);
ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode());
addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td);
td = tr.td();
if (!Utilities.noString(c.getDisplay()))
VersionComparisonAnnotation.render(c.getDisplayElement(), td).addText(c.getDisplay());
renderStatus(c.getDisplayElement(), td).addText(c.getDisplay());
else if (VersionComparisonAnnotation.hasDeleted(c, "display")) {
StringType d = (StringType) VersionComparisonAnnotation.getDeletedItem(c, "display");
VersionComparisonAnnotation.render(d, td).addText(d.primitiveValue());
renderStatus(d, td).addText(d.primitiveValue());
} else if (cc != null && !Utilities.noString(cc.getDisplay()))
td.style("color: #cccccc").addText(cc.getDisplay());

View File

@ -187,6 +187,7 @@ public class RenderingContext {
private DateTimeFormatter dateYearMonthFormat;
private boolean copyButton;
private ProfileKnowledgeProvider pkp;
private String changeVersion;
private Map<KnownLinkType, String> links = new HashMap<>();
/**
@ -254,6 +255,7 @@ public class RenderingContext {
res.copyButton = copyButton;
res.pkp = pkp;
res.defaultStandardsStatus = defaultStandardsStatus;
res.changeVersion = changeVersion;
res.terminologyServiceOptions = terminologyServiceOptions.copy();
return res;
@ -709,4 +711,14 @@ public class RenderingContext {
return this;
}
public String getChangeVersion() {
return changeVersion;
}
public RenderingContext setChangeVersion(String changeVersion) {
this.changeVersion = changeVersion;
return this;
}
}

View File

@ -66,6 +66,7 @@ import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities;
@ -888,5 +889,21 @@ public class CodeSystemUtilities {
ConceptPropertyComponent cp = getProperty(def, property);
return cp == null ? null : cp.getValue();
}
public static boolean hasMarkdownInDefinitions(CodeSystem cs, MarkDownProcessor md) {
return hasMarkdownInDefinitions(cs.getConcept(), md);
}
private static boolean hasMarkdownInDefinitions(List<ConceptDefinitionComponent> concepts, MarkDownProcessor md) {
for (ConceptDefinitionComponent c : concepts) {
if (c.hasDefinition() && md.isProbablyMarkdown(c.getDefinition(), true)) {
return true;
}
if (c.hasConcept() && hasMarkdownInDefinitions(c.getConcept(), md)) {
return true;
}
}
return false;
}
}

View File

@ -114,9 +114,12 @@ import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
@ -127,20 +130,18 @@ public class ValueSetExpander extends ValueSetProcessBase {
private ValueSet focus;
private List<String> allErrors = new ArrayList<>();
private List<String> requiredSupplements = new ArrayList<>();
private int maxExpansionSize = 1000;
private WorkingContext dwc = new WorkingContext();
private boolean checkCodesWhenExpanding;
private boolean includeAbstract = true;
public ValueSetExpander(IWorkerContext context) {
this.context = context;
public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext) {
super(context, opContext);
}
public ValueSetExpander(IWorkerContext context, List<String> allErrors) {
super();
this.context = context;
public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext, List<String> allErrors) {
super(context, opContext);
this.allErrors = allErrors;
}
@ -151,7 +152,8 @@ public class ValueSetExpander extends ValueSetProcessBase {
private ValueSetExpansionContainsComponent addCode(WorkingContext wc, String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams,
boolean isAbstract, boolean inactive, List<ValueSet> filters, boolean noInactive, boolean deprecated, List<ValueSetExpansionPropertyComponent> vsProp,
List<ConceptPropertyComponent> csProps, List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> expProps, List<Extension> csExtList, List<Extension> vsExtList, ValueSetExpansionComponent exp) throws ETooCostly {
opContext.deadCheck();
if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp))
return null;
if (noInactive && inactive) {
@ -311,6 +313,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly {
opContext.deadCheck();
focus.checkNoModifiers("Expansion.contains", "expanding");
ValueSetExpansionContainsComponent np = null;
for (String code : getCodesForConcept(focus, expParams)) {
@ -360,6 +363,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
private void addCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters,
ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly {
opContext.deadCheck();
def.checkNoModifiers("Code in Code System", "expanding");
if (exclusion != null) {
if (exclusion.getCode().equals(def.getCode()))
@ -432,6 +436,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
private void excludeCodes(WorkingContext wc, ConceptSetComponent exc, List<ValueSetExpansionParameterComponent> params, String ctxt) throws FHIRException {
opContext.deadCheck();
exc.checkNoModifiers("Compose.exclude", "expanding");
if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) {
wc.getExcludeSystems().add(exc.getSystem());
@ -460,6 +465,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) {
opContext.deadCheck();
for (ValueSetExpansionContainsComponent c : expand.getContains()) {
excludeCode(wc, c.getSystem(), c.getCode());
}
@ -475,8 +481,11 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) {
allErrors.clear();
try {
opContext.seeContext(source.getVersionedUrl());
return expandInternal(source, expParams);
} catch (NoTerminologyServiceException e) {
// well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
@ -486,6 +495,12 @@ public class ValueSetExpander extends ValueSetProcessBase {
// well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
// that might fail too, but it might not, later.
return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors);
} catch (TerminologyServiceProtectionException e) {
if (opContext.isOriginal()) {
return new ValueSetExpansionOutcome(e.getMessage(), e.getError(), allErrors);
} else {
throw e;
}
} catch (ETooCostly e) {
return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors);
} catch (Exception e) {
@ -574,8 +589,8 @@ public class ValueSetExpander extends ValueSetProcessBase {
if (dwc.getTotal() >= 0) {
focus.getExpansion().setTotal(dwc.getTotal());
}
if (!requiredSupplements.isEmpty()) {
return new ValueSetExpansionOutcome("Required supplements not found: "+requiredSupplements.toString(), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors);
if (!requiredSupplements.isEmpty()) {
return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors);
}
if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) {
focus.setCompose(null);
@ -656,7 +671,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
expParams = expParams.copy();
expParams.addParameter("activeOnly", true);
}
ValueSetExpansionOutcome vso = new ValueSetExpander(context, allErrors).expand(vs, expParams);
ValueSetExpansionOutcome vso = new ValueSetExpander(context, opContext.copy(), allErrors).expand(vs, expParams);
if (vso.getError() != null) {
addErrors(vso.getAllErrors());
throw fail("Unable to expand imported value set "+vs.getUrl()+": " + vso.getError());
@ -697,6 +712,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
public void copyExpansion(WorkingContext wc,List<ValueSetExpansionContainsComponent> list) {
opContext.deadCheck();
for (ValueSetExpansionContainsComponent cc : list) {
ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent();
n.setSystem(cc.getSystem());
@ -725,6 +741,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
private void copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly {
opContext.deadCheck();
for (ValueSetExpansionContainsComponent c : list) {
c.checkNoModifiers("Imported Expansion in Code System", "expanding");
ValueSetExpansionContainsComponent np = addCode(dwc, c.getSystem(), c.getCode(), c.getDisplay(), vsSrc.getLanguage(), parent, null, expParams, c.getAbstract(), c.getInactive(),
@ -734,6 +751,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension {
opContext.deadCheck();
inc.checkNoModifiers("Compose.include", "expanding");
List<ValueSet> imports = new ArrayList<ValueSet>();
for (CanonicalType imp : inc.getValueSet()) {
@ -764,6 +782,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List<ValueSet> imports, Parameters expParams, List<Extension> extensions, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws FHIRException, CodeSystemProviderExtension, ETooCostly {
opContext.deadCheck();
CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem());
if (csp != null) {
csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps);
@ -800,6 +819,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, Resource vsSrc) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException, ETooCostly {
opContext.deadCheck();
if (cs == null) {
if (context.isNoTerminologyServer())
throw failTSE("Unable to find code system " + inc.getSystem().toString());
@ -884,6 +904,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
private void processFilter(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams,
List<ValueSet> imports, CodeSystem cs, boolean noInactive, ConceptSetFilterComponent fc, WorkingContext wc, List<WorkingContext> filters)
throws ETooCostly {
opContext.deadCheck();
if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) {
// special: all codes in the target code system under the value
ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
@ -919,6 +940,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) {
if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) {
for (String code : getCodesForConcept(def, expParams)) {
opContext.deadCheck();
ValueSetExpansionContainsComponent t = addCode(wc, inc.getSystem(), code, def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def),
imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), null, def.getExtension(), null, exp);
}

View File

@ -0,0 +1,86 @@
package org.hl7.fhir.r5.terminologies.utilities;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.OperationOutcome.IssueType;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import java.util.ArrayList;
public class TerminologyOperationContext {
public class TerminologyServiceProtectionException extends FHIRException {
private TerminologyServiceErrorClass error;
private IssueType type;
protected TerminologyServiceProtectionException(String message, TerminologyServiceErrorClass error, IssueType type) {
super(message);
this.error = error;
this.type = type;
}
public TerminologyServiceErrorClass getError() {
return error;
}
public IssueType getType() {
return type;
}
}
public static boolean debugging = java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments().toString().indexOf("-agentlib:jdwp") > 0;
private static final int EXPANSION_DEAD_TIME_SECS = 60;
private long deadTime;
private List<String> contexts = new ArrayList<>();
private IWorkerContext worker;
private boolean original;
public TerminologyOperationContext(IWorkerContext worker) {
super();
this.worker = worker;
this.original = true;
if (EXPANSION_DEAD_TIME_SECS == 0 || debugging) {
deadTime = 0;
} else {
deadTime = System.currentTimeMillis() + (EXPANSION_DEAD_TIME_SECS * 1000);
}
}
private TerminologyOperationContext() {
super();
}
public TerminologyOperationContext copy() {
TerminologyOperationContext ret = new TerminologyOperationContext();
ret.worker = worker;
ret.contexts.addAll(contexts);
ret.deadTime = deadTime;
return ret;
}
public void deadCheck() {
if (deadTime != 0 && System.currentTimeMillis() > deadTime) {
throw new TerminologyServiceProtectionException(worker.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_TIME, contexts.get(0), EXPANSION_DEAD_TIME_SECS), TerminologyServiceErrorClass.TOO_COSTLY, IssueType.TOOCOSTLY);
}
}
public void seeContext(String context) {
if (contexts.contains(context)) {
throw new TerminologyServiceProtectionException(worker.formatMessage(I18nConstants.VALUESET_CIRCULAR_REFERENCE, context, contexts.toString()), TerminologyServiceErrorClass.BUSINESS_RULE, IssueType.BUSINESSRULE);
}
contexts.add(context);
}
public boolean isOriginal() {
return original;
}
}

View File

@ -25,7 +25,14 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
public class ValueSetProcessBase {
protected IWorkerContext context;
protected TerminologyOperationContext opContext;
protected List<String> requiredSupplements = new ArrayList<>();
protected ValueSetProcessBase(IWorkerContext context, TerminologyOperationContext opContext) {
super();
this.context = context;
this.opContext = opContext;
}
public static class AlternateCodesProcessingRules {
private boolean all;
private List<String> uses = new ArrayList<>();
@ -106,7 +113,9 @@ public class ValueSetProcessBase {
break;
}
result.setCode(type);
result.addLocation(location);
if (location != null) {
result.addLocation(location);
}
result.getDetails().setText(message);
ArrayList<OperationOutcomeIssueComponent> list = new ArrayList<>();
list.add(result);

View File

@ -47,6 +47,7 @@ import org.hl7.fhir.exceptions.NoTerminologyServiceException;
import org.hl7.fhir.r5.context.ContextUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r5.extensions.ExtensionConstants;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
@ -79,6 +80,7 @@ import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider;
import org.hl7.fhir.r5.terminologies.providers.SpecialCodeSystem;
import org.hl7.fhir.r5.terminologies.providers.URICodeSystem;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase;
import org.hl7.fhir.r5.utils.ToolingExtensions;
@ -106,18 +108,18 @@ public class ValueSetValidator extends ValueSetProcessBase {
private Set<String> unknownSystems;
private boolean throwToServer;
public ValueSetValidator(ValidationOptions options, ValueSet source, IWorkerContext context, Parameters expansionProfile, TerminologyCapabilities txCaps) {
public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, Parameters expansionProfile, TerminologyCapabilities txCaps) {
super(context, opContext);
this.valueset = source;
this.context = context;
this.options = options;
this.expansionProfile = expansionProfile;
this.txCaps = txCaps;
analyseValueSet();
}
public ValueSetValidator(ValidationOptions options, ValueSet source, IWorkerContext context, ValidationContextCarrier ctxt, Parameters expansionProfile, TerminologyCapabilities txCaps) {
public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, ValidationContextCarrier ctxt, Parameters expansionProfile, TerminologyCapabilities txCaps) {
super(context, opContext);
this.valueset = source;
this.context = context;
this.options = options.copy();
this.options.setEnglishOk(true);
this.localContext = ctxt;
@ -143,6 +145,13 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
private void analyseValueSet() {
if (valueset != null) {
opContext.seeContext(valueset.getVersionedUrl());
for (Extension s : valueset.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) {
requiredSupplements.add(s.getValue().primitiveValue());
}
}
altCodeParams.seeParameters(expansionProfile);
altCodeParams.seeValueSet(valueset);
if (localContext != null) {
@ -158,6 +167,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
private void analyseComponent(ConceptSetComponent i) {
opContext.deadCheck();
if (i.getSystemElement().hasExtension(ToolingExtensions.EXT_VALUESET_SYSTEM)) {
String ref = i.getSystemElement().getExtensionString(ToolingExtensions.EXT_VALUESET_SYSTEM);
if (ref.startsWith("#")) {
@ -179,6 +189,8 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
public ValidationResult validateCode(String path, CodeableConcept code) throws FHIRException {
opContext.deadCheck();
// first, we validate the codings themselves
ValidationProcessInfo info = new ValidationProcessInfo();
@ -250,7 +262,9 @@ public class ValueSetValidator extends ValueSetProcessBase {
info.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, path, msg));
}
}
if (info.hasErrors()) {
if (!checkRequiredSupplements(info)) {
return new ValidationResult(IssueSeverity.ERROR, info.getIssues().get(info.getIssues().size()-1).getDetails().getText(), info.getIssues());
} else if (info.hasErrors()) {
ValidationResult res = new ValidationResult(IssueSeverity.ERROR, info.summary(), info.getIssues());
if (foundCoding != null) {
ConceptDefinitionComponent cd = new ConceptDefinitionComponent(foundCoding.getCode());
@ -279,6 +293,13 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
}
private boolean checkRequiredSupplements(ValidationProcessInfo info) {
if (!requiredSupplements.isEmpty()) {
info.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, null, context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements))));
}
return requiredSupplements.isEmpty();
}
private boolean valueSetDependsOn(String system, String version) {
for (ConceptSetComponent inc : valueset.getCompose().getInclude()) {
if (system.equals(inc.getSystem()) && (version == null || inc.getVersion() == null || version.equals(inc.getVersion()))) {
@ -315,7 +336,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
return t;
}
}
CodeSystem cs = context.fetchCodeSystem(system, version);
CodeSystem cs = context.fetchSupplementedCodeSystem(system, version);
if (cs == null) {
cs = findSpecialCodeSystem(system, version);
}
@ -342,6 +363,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
public ValidationResult validateCode(String path, Coding code) throws FHIRException {
opContext.deadCheck();
String warningMessage = null;
// first, we validate the concept itself
@ -460,6 +482,9 @@ public class ValueSetValidator extends ValueSetProcessBase {
inInclude = checkInclude(code, vi);
}
String wv = vi.getVersion(system, code.getVersion());
if (!checkRequiredSupplements(info)) {
return new ValidationResult(IssueSeverity.ERROR, issues.get(issues.size()-1).getDetails().getText(), issues);
}
// then, if we have a value set, we check it's in the value set
@ -597,6 +622,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
private ValidationResult findCodeInExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains) {
for (ValueSetExpansionContainsComponent containsComponent: contains) {
opContext.deadCheck();
if (containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) {
ConceptDefinitionComponent ccd = new ConceptDefinitionComponent();
ccd.setCode(containsComponent.getCode());
@ -623,6 +649,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
private boolean checkExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains, VersionInfo vi) {
for (ValueSetExpansionContainsComponent containsComponent: contains) {
opContext.deadCheck();
if (containsComponent.hasSystem() && containsComponent.hasCode() && containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) {
vi.setExpansionVersion(containsComponent.getVersion());
return true;
@ -667,6 +694,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
for (ConceptDefinitionDesignationComponent ds : cc.getDesignation()) {
opContext.deadCheck();
if (isOkLanguage(ds.getLanguage())) {
b.append("'"+ds.getValue()+"'");
if (code.getDisplay().equalsIgnoreCase(ds.getValue())) {
@ -688,6 +716,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
}
for (ConceptReferenceDesignationComponent ds : vs.getCc().getDesignation()) {
opContext.deadCheck();
if (isOkLanguage(ds.getLanguage())) {
b.append("'"+ds.getValue()+"'");
if (code.getDisplay().equalsIgnoreCase(ds.getValue())) {
@ -732,6 +761,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
return null;
// if it has an expansion
for (ValueSetExpansionContainsComponent exp : valueset.getExpansion().getContains()) {
opContext.deadCheck();
if (system.equals(exp.getSystem()) && code.equals(exp.getCode())) {
ConceptReferenceComponent cc = new ConceptReferenceComponent();
cc.setDisplay(exp.getDisplay());
@ -810,6 +840,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
private ConceptDefinitionComponent findCodeInConcept(ConceptDefinitionComponent concept, String code, AlternateCodesProcessingRules altCodeRules) {
opContext.deadCheck();
if (code.equals(concept.getCode())) {
return concept;
}
@ -879,6 +910,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
int i = 0;
for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) {
opContext.deadCheck();
if (vsi.hasValueSet()) {
for (CanonicalType u : vsi.getValueSet()) {
if (!checkForCodeInValueSet(code, u.getValue(), sys, problems)) {
@ -963,6 +995,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
*/
private boolean checkSystems(List<ValueSetExpansionContainsComponent> contains, String code, Set<String> systems, List<String> problems) {
for (ValueSetExpansionContainsComponent c: contains) {
opContext.deadCheck();
if (c.getCode().equals(code)) {
systems.add(c.getSystem());
}
@ -976,6 +1009,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
if (valueset == null) {
return false;
}
opContext.deadCheck();
checkCanonical(info.getIssues(), path, valueset, valueset);
Boolean result = false;
VersionInfo vi = new VersionInfo(this);
@ -1010,6 +1044,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
private Boolean inComponent(String path, ConceptSetComponent vsi, int vsiIndex, String system, String version, String code, boolean only, ValidationProcessInfo info) throws FHIRException {
opContext.deadCheck();
boolean ok = true;
if (vsi.hasValueSet()) {
@ -1189,6 +1224,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
public boolean validateCodeInConceptList(String code, CodeSystem def, List<ConceptDefinitionComponent> list, AlternateCodesProcessingRules altCodeRules) {
opContext.deadCheck();
if (def.getCaseSensitive()) {
for (ConceptDefinitionComponent cc : list) {
if (cc.getCode().equals(code)) {
@ -1219,7 +1255,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
return inner.get(url);
}
ValueSet vs = context.fetchResource(ValueSet.class, url, valueset);
ValueSetValidator vsc = new ValueSetValidator(options, vs, context, localContext, expansionProfile, txCaps);
ValueSetValidator vsc = new ValueSetValidator(context, opContext.copy(), options, vs, localContext, expansionProfile, txCaps);
vsc.setThrowToServer(throwToServer);
inner.put(url, vsc);
return vsc;

View File

@ -2,20 +2,22 @@ package org.hl7.fhir.r5.test.utils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.utilities.*;
import org.hl7.fhir.utilities.json.JsonUtilities;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonNull;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.model.JsonPrimitive;
import org.hl7.fhir.utilities.json.model.JsonProperty;
import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.hl7.fhir.utilities.settings.FhirSettings;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import org.hl7.fhir.utilities.tests.BaseTestingUtilities;
import javax.xml.parsers.DocumentBuilder;
@ -28,21 +30,53 @@ import java.util.Map;
public class CompareUtilities extends BaseTestingUtilities {
private static final boolean SHOW_DIFF = true;
public static String createNotEqualMessage(final String message, final String expected, final String actual) {
private JsonObject externals;
public String createNotEqualMessage(final String message, final String expected, final String actual) {
return new StringBuilder()
.append(message).append('\n')
.append("Expected :").append(expected).append('\n')
.append("Actual :").append(actual).toString();
.append("Expected :").append(presentExpected(expected)).append('\n')
.append("Actual :").append("\""+actual+"\"").toString();
}
private String presentExpected(String expected) {
if (expected.startsWith("$") && expected.endsWith("$")) {
if (expected.startsWith("$choice:")) {
return "Contains one of "+readChoices(expected.substring(8, expected.length()-1)).toString();
} else if (expected.startsWith("$fragments:")) {
List<String> fragments = readChoices(expected.substring(11, expected.length()-1));
return "Contains all of "+fragments.toString();
} else if (expected.startsWith("$external:")) {
String[] cmd = expected.substring(1, expected.length() - 1).split(":");
if (externals != null) {
String s = externals.asString(cmd[1]);
return "\""+s+"\" (Ext)";
} else {
List<String> fragments = readChoices(cmd[2]);
return "Contains all of "+fragments.toString()+" (because no external string provided for "+cmd[1]+")";
}
} else {
switch (expected) {
case "$$" : return "$$";
case "$instant$": return "\"An Instant\"";
case "$uuid$": return "\"A Uuid\"";
default: return "Unhandled template: "+expected;
}
}
} else {
return "\""+expected+"\"";
}
}
public static String checkXMLIsSame(InputStream expected, InputStream actual) throws Exception {
String result = compareXml(expected, actual);
CompareUtilities self = new CompareUtilities();
String result = self.compareXml(expected, actual);
return result;
}
public static String checkXMLIsSame(String expected, String actual) throws Exception {
String result = compareXml(expected, actual);
CompareUtilities self = new CompareUtilities();
String result = self.compareXml(expected, actual);
if (result != null && SHOW_DIFF) {
String diff = getDiffTool();
if (diff != null && new File(diff).exists() || Utilities.isToken(diff)) {
@ -52,7 +86,7 @@ public class CompareUtilities extends BaseTestingUtilities {
return result;
}
private static String getDiffTool() throws IOException {
private static String getDiffTool() throws IOException {
if (FhirSettings.hasDiffToolPath()) {
return FhirSettings.getDiffToolPath();
} else if (System.getenv("ProgramFiles") != null) {
@ -62,15 +96,15 @@ public class CompareUtilities extends BaseTestingUtilities {
}
}
private static String compareXml(InputStream expected, InputStream actual) throws Exception {
private String compareXml(InputStream expected, InputStream actual) throws Exception {
return compareElements("", loadXml(expected).getDocumentElement(), loadXml(actual).getDocumentElement());
}
private static String compareXml(String expected, String actual) throws Exception {
private String compareXml(String expected, String actual) throws Exception {
return compareElements("", loadXml(expected).getDocumentElement(), loadXml(actual).getDocumentElement());
}
private static String compareElements(String path, Element expectedElement, Element actualElement) {
private String compareElements(String path, Element expectedElement, Element actualElement) {
if (!namespacesMatch(expectedElement.getNamespaceURI(), actualElement.getNamespaceURI()))
return createNotEqualMessage("Namespaces differ at " + path, expectedElement.getNamespaceURI(), actualElement.getNamespaceURI());
if (!expectedElement.getLocalName().equals(actualElement.getLocalName()))
@ -109,18 +143,18 @@ public class CompareUtilities extends BaseTestingUtilities {
return null;
}
private static boolean namespacesMatch(String ns1, String ns2) {
private boolean namespacesMatch(String ns1, String ns2) {
return ns1 == null ? ns2 == null : ns1.equals(ns2);
}
private static Object normalise(String text) {
private String normalise(String text) {
String result = text.trim().replace('\r', ' ').replace('\n', ' ').replace('\t', ' ');
while (result.contains(" "))
result = result.replace(" ", " ");
return result;
}
private static String compareAttributes(String path, NamedNodeMap expected, NamedNodeMap actual) {
private String compareAttributes(String path, NamedNodeMap expected, NamedNodeMap actual) {
for (int i = 0; i < expected.getLength(); i++) {
Node expectedNode = expected.item(i);
@ -140,7 +174,7 @@ public class CompareUtilities extends BaseTestingUtilities {
return null;
}
private static boolean sameBytes(byte[] b1, byte[] b2) {
private boolean sameBytes(byte[] b1, byte[] b2) {
if (b1.length == 0 || b2.length == 0)
return false;
if (b1.length != b2.length)
@ -151,21 +185,21 @@ public class CompareUtilities extends BaseTestingUtilities {
return true;
}
private static byte[] unBase64(String text) {
private byte[] unBase64(String text) {
return Base64.decodeBase64(text);
}
private static Node skipBlankText(Node node) {
private Node skipBlankText(Node node) {
while (node != null && (((node.getNodeType() == Node.TEXT_NODE) && StringUtils.isWhitespace(node.getTextContent())) || (node.getNodeType() == Node.COMMENT_NODE)))
node = node.getNextSibling();
return node;
}
private static Document loadXml(String fn) throws Exception {
private Document loadXml(String fn) throws Exception {
return loadXml(new FileInputStream(fn));
}
private static Document loadXml(InputStream fn) throws Exception {
private Document loadXml(InputStream fn) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
@ -179,12 +213,14 @@ public class CompareUtilities extends BaseTestingUtilities {
return builder.parse(fn);
}
public static String checkJsonSrcIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException {
return checkJsonSrcIsSame(expected, actual, true);
public static String checkJsonSrcIsSame(String expected, String actual, JsonObject externals) throws FileNotFoundException, IOException {
return checkJsonSrcIsSame(expected, actual, true, externals);
}
public static String checkJsonSrcIsSame(String expectedString, String actualString, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException {
String result = compareJsonSrc(expectedString, actualString);
public static String checkJsonSrcIsSame(String expectedString, String actualString, boolean showDiff, JsonObject externals) throws FileNotFoundException, IOException {
CompareUtilities self = new CompareUtilities();
self.externals = externals;
String result = self.compareJsonSrc(expectedString, actualString);
if (result != null && SHOW_DIFF && showDiff) {
String diff = null;
if (System.getProperty("os.name").contains("Linux"))
@ -217,8 +253,9 @@ public class CompareUtilities extends BaseTestingUtilities {
return result;
}
public static String checkJsonIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException {
String result = compareJson(expected, actual);
public static String checkJsonIsSame(String expected, String actual) throws FileNotFoundException, IOException {
CompareUtilities self = new CompareUtilities();
String result = self.compareJson(expected, actual);
if (result != null && SHOW_DIFF) {
String diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe");
List<String> command = new ArrayList<String>();
@ -232,22 +269,22 @@ public class CompareUtilities extends BaseTestingUtilities {
return result;
}
private static String compareJsonSrc(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException {
JsonObject actualJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(actual);
JsonObject expectedJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(expected);
private String compareJsonSrc(String expected, String actual) throws FileNotFoundException, IOException {
JsonObject actualJsonObject = JsonParser.parseObject(actual);
JsonObject expectedJsonObject = JsonParser.parseObject(expected);
return compareObjects("", expectedJsonObject, actualJsonObject);
}
private static String compareJson(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException {
JsonObject actualJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(actual));
JsonObject expectedJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(expected));
private String compareJson(String expected, String actual) throws FileNotFoundException, IOException {
JsonObject actualJsonObject = JsonParser.parseObject(TextFile.fileToString(actual));
JsonObject expectedJsonObject = JsonParser.parseObject(TextFile.fileToString(expected));
return compareObjects("", expectedJsonObject, actualJsonObject);
}
private static String compareObjects(String path, JsonObject expectedJsonObject, JsonObject actualJsonObject) {
private String compareObjects(String path, JsonObject expectedJsonObject, JsonObject actualJsonObject) {
List<String> optionals = listOptionals(expectedJsonObject);
for (Map.Entry<String, JsonElement> en : actualJsonObject.entrySet()) {
String n = en.getKey();
for (JsonProperty en : actualJsonObject.getProperties()) {
String n = en.getName();
if (!n.equals("fhir_comments")) {
if (expectedJsonObject.has(n)) {
String s = compareNodes(path + '.' + n, expectedJsonObject.get(n), en.getValue());
@ -257,8 +294,8 @@ public class CompareUtilities extends BaseTestingUtilities {
return "properties differ at " + path + ": missing property " + n;
}
}
for (Map.Entry<String, JsonElement> en : expectedJsonObject.entrySet()) {
String n = en.getKey();
for (JsonProperty en : expectedJsonObject.getProperties()) {
String n = en.getName();
if (!n.equals("fhir_comments") && !n.equals("$optional$") && !optionals.contains(n)) {
if (!actualJsonObject.has(n))
return "properties differ at " + path + ": missing property " + n;
@ -267,38 +304,44 @@ public class CompareUtilities extends BaseTestingUtilities {
return null;
}
private static List<String> listOptionals(JsonObject expectedJsonObject) {
private List<String> listOptionals(JsonObject expectedJsonObject) {
List<String> res = new ArrayList<>();
if (expectedJsonObject.has("$optional-properties$")) {
res.add("$optional-properties$");
for (String s : JsonUtilities.strings(expectedJsonObject.getAsJsonArray("$optional-properties$"))) {
for (String s : expectedJsonObject.getStrings("$optional-properties$")) {
res.add(s);
}
}
return res;
}
private static String compareNodes(String path, JsonElement expectedJsonElement, JsonElement actualJsonElement) {
if (actualJsonElement.getClass() != expectedJsonElement.getClass())
return createNotEqualMessage("properties differ at " + path, expectedJsonElement.getClass().getName(), actualJsonElement.getClass().getName());
else if (actualJsonElement instanceof JsonPrimitive) {
private String compareNodes(String path, JsonElement expectedJsonElement, JsonElement actualJsonElement) {
if (!(expectedJsonElement instanceof JsonPrimitive && actualJsonElement instanceof JsonPrimitive)) {
if (actualJsonElement.getClass() != expectedJsonElement.getClass()) {
return createNotEqualMessage("properties differ at " + path, expectedJsonElement.getClass().getName(), actualJsonElement.getClass().getName());
}
}
if (actualJsonElement instanceof JsonPrimitive) {
JsonPrimitive actualJsonPrimitive = (JsonPrimitive) actualJsonElement;
JsonPrimitive expectedJsonPrimitive = (JsonPrimitive) expectedJsonElement;
if (actualJsonPrimitive.isBoolean() && expectedJsonPrimitive.isBoolean()) {
if (actualJsonPrimitive.getAsBoolean() != expectedJsonPrimitive.getAsBoolean())
return createNotEqualMessage("boolean property values differ at " + path , expectedJsonPrimitive.getAsString(), actualJsonPrimitive.getAsString());
} else if (actualJsonPrimitive.isString() && expectedJsonPrimitive.isString()) {
String actualJsonString = actualJsonPrimitive.getAsString();
String expectedJsonString = expectedJsonPrimitive.getAsString();
if (actualJsonPrimitive.isJsonBoolean() && expectedJsonPrimitive.isJsonBoolean()) {
if (actualJsonPrimitive.asBoolean() != expectedJsonPrimitive.asBoolean())
return createNotEqualMessage("boolean property values differ at " + path , expectedJsonPrimitive.asString(), actualJsonPrimitive.asString());
} else if (actualJsonPrimitive.isJsonString() && expectedJsonPrimitive.isJsonString()) {
String actualJsonString = actualJsonPrimitive.asString();
String expectedJsonString = expectedJsonPrimitive.asString();
if (!(actualJsonString.contains("<div") && expectedJsonString.contains("<div")))
if (!matches(actualJsonString, expectedJsonString))
if (!sameBytes(unBase64(actualJsonString), unBase64(expectedJsonString)))
return createNotEqualMessage("string property values differ at " + path, expectedJsonString, actualJsonString);
} else if (actualJsonPrimitive.isNumber() && expectedJsonPrimitive.isNumber()) {
if (!actualJsonPrimitive.getAsString().equals(expectedJsonPrimitive.getAsString()))
return createNotEqualMessage("number property values differ at " + path, expectedJsonPrimitive.getAsString(), actualJsonPrimitive.getAsString());
} else
return createNotEqualMessage("property types differ at " + path, expectedJsonPrimitive.getAsString(), actualJsonPrimitive.getAsString());
} else if (actualJsonPrimitive.isJsonNumber() && expectedJsonPrimitive.isJsonNumber()) {
if (!actualJsonPrimitive.asString().equals(expectedJsonPrimitive.asString()))
return createNotEqualMessage("number property values differ at " + path, expectedJsonPrimitive.asString(), actualJsonPrimitive.asString());
} else if (expectedJsonElement instanceof JsonNull) {
return actualJsonPrimitive instanceof JsonNull ? null : createNotEqualMessage("null Properties differ at " + path, "null", actualJsonPrimitive.asString());
} else {
return createNotEqualMessage("property types differ at " + path, expectedJsonPrimitive.asString(), actualJsonPrimitive.asString());
}
} else if (actualJsonElement instanceof JsonObject) {
String s = compareObjects(path, (JsonObject) expectedJsonElement, (JsonObject) actualJsonElement);
if (!Utilities.noString(s))
@ -308,12 +351,15 @@ public class CompareUtilities extends BaseTestingUtilities {
JsonArray expectedArray = (JsonArray) expectedJsonElement;
int expectedMin = countExpectedMin(expectedArray);
if (actualArray.size() > expectedArray.size() || actualArray.size() < expectedMin)
return createNotEqualMessage("array item count differs at " + path, Integer.toString(expectedArray.size()), Integer.toString(actualArray.size()));
int as = actualArray.size();
int es = expectedArray.size();
int oc = optionalCount(expectedArray);
if (as > es || as < expectedMin)
return createNotEqualMessage("array item count differs at " + path, Integer.toString(es), Integer.toString(as));
int c = 0;
for (int i = 0; i < expectedArray.size(); i++) {
if (c >= actualArray.size()) {
if (i == expectedArray.size() - 1 && isOptional(expectedArray.get(i))) {
for (int i = 0; i < es; i++) {
if (c >= as) {
if (i >= es - oc && isOptional(expectedArray.get(i))) {
return null; // this is OK
} else {
return "One or more array items did not match at "+path+" starting at index "+i;
@ -327,18 +373,30 @@ public class CompareUtilities extends BaseTestingUtilities {
c++;
}
}
} else if (actualJsonElement instanceof JsonNull) {
} else
return "unhandled property " + actualJsonElement.getClass().getName();
return null;
}
private static boolean isOptional(JsonElement e) {
return e.isJsonObject() && e.getAsJsonObject().has("$optional$");
private int optionalCount(JsonArray arr) {
int c = 0;
for (JsonElement e : arr) {
if (e.isJsonObject()) {
JsonObject j = e.asJsonObject();
if (j.has("$optional$") && j.asBoolean("$optional$")) {
c++;
}
}
}
return c;
}
private static int countExpectedMin(JsonArray array) {
private boolean isOptional(JsonElement e) {
return e.isJsonObject() && e.asJsonObject().has("$optional$");
}
private int countExpectedMin(JsonArray array) {
int count = array.size();
for (JsonElement e : array) {
if (isOptional(e)) {
@ -348,19 +406,33 @@ public class CompareUtilities extends BaseTestingUtilities {
return count;
}
private static boolean matches(String actualJsonString, String expectedJsonString) {
private boolean matches(String actualJsonString, String expectedJsonString) {
if (expectedJsonString.startsWith("$") && expectedJsonString.endsWith("$")) {
if (expectedJsonString.startsWith("$choice:")) {
return Utilities.existsInList(actualJsonString, readChoices(8, expectedJsonString));
return Utilities.existsInList(actualJsonString, readChoices(expectedJsonString.substring(8, expectedJsonString.length()-1)));
} else if (expectedJsonString.startsWith("$fragments:")) {
List<String> fragments = readChoices(11, expectedJsonString);
List<String> fragments = readChoices(expectedJsonString.substring(11, expectedJsonString.length()-1));
for (String f : fragments) {
if (!actualJsonString.toLowerCase().contains(f.toLowerCase())) {
return false;
}
}
return true;
} else if (expectedJsonString.startsWith("$external:")) {
String[] cmd = expectedJsonString.substring(1, expectedJsonString.length() - 1).split("\\:");
if (externals != null) {
String s = externals.asString(cmd[1]);
return actualJsonString.equals(s);
} else {
List<String> fragments = readChoices(cmd[2]);
for (String f : fragments) {
if (!actualJsonString.toLowerCase().contains(f.toLowerCase())) {
return false;
}
}
return true;
}
} else {
switch (expectedJsonString) {
case "$$" : return true;
@ -375,21 +447,21 @@ public class CompareUtilities extends BaseTestingUtilities {
}
}
private static List<String> readChoices(int offset, String s) {
private List<String> readChoices(String s) {
List<String> list = new ArrayList<>();
s = s.substring(offset, s.length()-1);
for (String p : s.split("\\|")) {
list.add(p);
}
return list;
}
public static String checkTextIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException {
public static String checkTextIsSame(String expected, String actual) throws FileNotFoundException, IOException {
return checkTextIsSame(expected, actual, true);
}
public static String checkTextIsSame(String expectedString, String actualString, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException {
String result = compareText(expectedString, actualString);
public static String checkTextIsSame(String expectedString, String actualString, boolean showDiff) throws FileNotFoundException, IOException {
CompareUtilities self = new CompareUtilities();
String result = self.compareText(expectedString, actualString);
if (result != null && SHOW_DIFF && showDiff) {
String diff = null;
if (System.getProperty("os.name").contains("Linux"))
@ -423,7 +495,7 @@ public class CompareUtilities extends BaseTestingUtilities {
}
private static String compareText(String expectedString, String actualString) {
private String compareText(String expectedString, String actualString) {
for (int i = 0; i < Integer.min(expectedString.length(), actualString.length()); i++) {
if (expectedString.charAt(i) != actualString.charAt(i))
return createNotEqualMessage("Strings differ at character " + Integer.toString(i), String.valueOf(expectedString.charAt(i)), String.valueOf(actualString.charAt(i)));

View File

@ -151,11 +151,18 @@ public class DefinitionNavigator {
if (nameMap.containsKey(path)) {
DefinitionNavigator master = nameMap.get(path);
ElementDefinition cm = master.current();
// if (!cm.hasSlicing())
// throw new DefinitionException("Found slices with no slicing details at "+dn.current().getPath());
if (master.slices == null)
master.slices = new ArrayList<DefinitionNavigator>();
master.slices.add(dn);
if (diff) {
// slice name - jumped straight into slicing
children.add(dn);
} else {
if (!cm.hasSlicing()) {
throw new DefinitionException("Found slices with no slicing details at "+dn.current().getPath());
}
if (master.slices == null) {
master.slices = new ArrayList<DefinitionNavigator>();
}
master.slices.add(dn);
}
} else {
nameMap.put(path, dn);
children.add(dn);
@ -238,7 +245,11 @@ public class DefinitionNavigator {
@Override
public String toString() {
return current().getId();
return getId();
}
public String getId() {
return current() == null ? path : current().hasSliceName() ? current().getPath()+":"+current().getSliceName() : current().getPath();
}
public Base parent() {

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.r5.utils;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.MarkdownType;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
@ -11,17 +12,21 @@ public class PublicationHacker {
// this routine fixes up broken binding descriptions from past FHIR publications. All of them will be or are fixed in a later version,
// but fixing old versions is procedurally very difficult. Hence, these work around fixes here
public static StringType fixBindingDescriptions(IWorkerContext context, StringType s) {
StringType res = s.copy();
// ServiceRequest.code
if (res.getValue().contains("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]")) {
res.setValue(res.getValue().replace("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]", "LOINC is [preferred]("+Utilities.pathURL(VersionUtilities.getSpecUrl(context.getVersion()), "terminologies.html#preferred)")));
public static MarkdownType fixBindingDescriptions(IWorkerContext context, MarkdownType md) {
MarkdownType ret = null;
// ServiceRequest.code
if (md.getValue().contains("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]")) {
ret = md.copy();
ret.setValue(md.getValue().replace("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]", "LOINC is [preferred]("+Utilities.pathURL(VersionUtilities.getSpecUrl(context.getVersion()), "terminologies.html#preferred)")));
}
if (md.getValue().contains("[here](valueset-diagnostic-requests.html)")) {
if (ret == null) {
ret = md.copy();
}
if (res.getValue().contains("[here](valueset-diagnostic-requests.html)")) {
res.setValue(res.getValue().replace("[here](valueset-diagnostic-requests.html)", "here"));
}
return res;
ret.setValue(md.getValue().replace("[here](valueset-diagnostic-requests.html)", "here"));
}
return ret == null ? md : ret;
}
}

View File

@ -83,6 +83,7 @@ public class XVerExtensionManager {
String verSource = url.substring(20, 23);
String verTarget = VersionUtilities.getMajMin(context.getVersion());
String e = url.substring(54);
String r = e.contains(".") ? e.substring(0, e.indexOf(".")) : e;
JsonObject root = lists.get(verSource);
JsonObject path = root.getJsonObject(e);
if (path == null) {
@ -91,7 +92,11 @@ public class XVerExtensionManager {
StructureDefinition sd = new StructureDefinition();
sd.setUserData(XVER_EXT_MARKER, "true");
sd.setWebPath(PackageHacker.fixPackageUrl("https://hl7.org/fhir/versions.html#extensions"));
if (context.getResourceNamesAsSet().contains(r)) {
sd.setWebPath(Utilities.pathURL(context.getSpecUrl(), r.toLowerCase()+"-definitions.html#"+e));
} else {
sd.setWebPath(PackageHacker.fixPackageUrl("https://hl7.org/fhir/versions.html#extensions"));
}
sd.setUrl(url);
sd.setVersion(context.getVersion());
sd.setFhirVersion(FHIRVersion.fromCode(context.getVersion()));

View File

@ -60,7 +60,7 @@ public class ParsingTests {
r = new XmlParser().parse(b);
b = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeBytes(r);
String output = new String(b);
String msg = CompareUtilities.checkJsonSrcIsSame(src, output);
String msg = CompareUtilities.checkJsonSrcIsSame(src, output, null);
Assertions.assertTrue(msg == null, msg);
}

View File

@ -91,7 +91,7 @@ public class CompareUtilitiesTests implements ResourceLoaderTests {
final String expectedJSONPath = ROOT_JSON_TEST_PATH.resolve(expectedFileName).toString();
final String actualJSONPath = ROOT_JSON_TEST_PATH.resolve(actualFileName).toString();
final String actualOutput = CompareUtilities.checkJsonSrcIsSame(getResourceAsString(expectedJSONPath), getResourceAsString(actualJSONPath), false);
final String actualOutput = CompareUtilities.checkJsonSrcIsSame(getResourceAsString(expectedJSONPath), getResourceAsString(actualJSONPath), false, null);
if (expectedOutputFileName == null) {
assertNull(actualOutput);
} else {

View File

@ -1,3 +1,3 @@
string property values differ at .expectedArray[0]
Expected :expectedValue 1
Actual :unexpectedValue 1
Expected :"expectedValue 1"
Actual :"unexpectedValue 1"

View File

@ -1,3 +1,3 @@
array item count differs at .expectedArray
Expected :2
Actual :1
Expected :"2"
Actual :"1"

View File

@ -1,3 +1,3 @@
boolean property values differ at .expectedBoolean
Expected :true
Actual :false
Expected :"true"
Actual :"false"

View File

@ -1,3 +1,3 @@
number property values differ at .expectedNumber
Expected :123
Actual :789
Expected :"123"
Actual :"789"

View File

@ -1,3 +1,3 @@
property types differ at .expectedString
Expected :expected value
Actual :1
Expected :"expected value"
Actual :"1"

View File

@ -1,3 +1,3 @@
string property values differ at .expectedString
Expected :expected value
Actual :unexpected value
Expected :"expected value"
Actual :"unexpected value"

View File

@ -1,3 +1,3 @@
Attributes differ at /root/blah
Expected :dummyAtt
Actual :wrongwrongwrong
Expected :"dummyAtt"
Actual :"wrongwrongwrong"

View File

@ -1,3 +1,3 @@
Names differ at /root
Expected :nameSpacedNode
Actual :wrongNameSpacedNode
Expected :"nameSpacedNode"
Actual :"wrongNameSpacedNode"

View File

@ -1,3 +1,3 @@
Namespaces differ at /root
Expected :http://www.example.com/FOO
Actual :http://www.example.com/BAR
Expected :"http://www.example.com/FOO"
Actual :"http://www.example.com/BAR"

View File

@ -1,3 +1,3 @@
node type mismatch in children of /root/blah
Expected :1
Actual :1
Expected :"1"
Actual :"1"

View File

@ -1,3 +1,3 @@
Text differs at /root/blah
Expected :expected
Actual :different
Expected :"expected"
Actual :"different"

View File

@ -117,4 +117,12 @@ public class CommaSeparatedStringBuilder {
}
return b.toString();
}
public static String build(List<String> list) {
CommaSeparatedStringBuilder self = new CommaSeparatedStringBuilder();
for (String s : list) {
self.append(s);
}
return self.toString();
}
}

View File

@ -866,6 +866,7 @@ public class I18nConstants {
public static final String UNKNOWN_CODESYSTEM = "UNKNOWN_CODESYSTEM";
public static final String UNKNOWN_CODESYSTEM_VERSION = "UNKNOWN_CODESYSTEM_VERSION";
public static final String VALUESET_TOO_COSTLY = "VALUESET_TOO_COSTLY";
public static final String VALUESET_TOO_COSTLY_TIME = "VALUESET_TOO_COSTLY_TIME";
public static final String NO_VALID_DISPLAY_FOUND = "NO_VALID_DISPLAY_FOUND";
public static final String SD_NO_CONTEXT_WHEN_NOT_EXTENSION = "SD_NO_CONTEXT_WHEN_NOT_EXTENSION";
public static final String SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = "SD_CONTEXT_SHOULD_NOT_BE_ELEMENT";
@ -954,10 +955,15 @@ public class I18nConstants {
public static final String MSG_DEPRECATED = "MSG_DEPRECATED";
public static final String MSG_WITHDRAWN = "MSG_WITHDRAWN";
public static final String MSG_RETIRED = "MSG_RETIRED";
public static final String INACTIVE_CODE_WARNING = "INACTIVE_CODE_WARNING";
public static final String SD_EXTENSION_URL_MISSING = "SD_EXTENSION_URL_MISSING";
public static final String MSG_EXPERIMENTAL = "MSG_EXPERIMENTAL";
public static final String MSG_DRAFT = "MSG_DRAFT";
public static final String MSG_DEPRECATED_SRC = "MSG_DEPRECATED_SRC";
public static final String MSG_WITHDRAWN_SRC = "MSG_WITHDRAWN_SRC";
public static final String MSG_RETIRED_SRC = "MSG_RETIRED_SRC";
public static final String MSG_DRAFT_SRC = "MSG_DRAFT_SRC";
public static final String MSG_EXPERIMENTAL_SRC = "MSG_EXPERIMENTAL_SRC";
public static final String INACTIVE_CODE_WARNING = "INACTIVE_CODE_WARNING";
public static final String SD_EXTENSION_URL_MISSING = "SD_EXTENSION_URL_MISSING";
public static final String MSG_DEPENDS_ON_DEPRECATED = "MSG_DEPENDS_ON_DEPRECATED";
public static final String MSG_DEPENDS_ON_WITHDRAWN = "MSG_DEPENDS_ON_WITHDRAWN";
public static final String MSG_DEPENDS_ON_RETIRED = "MSG_DEPENDS_ON_RETIRED";
@ -972,6 +978,8 @@ public class I18nConstants {
public static final String CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO = "CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO";
public static final String CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG = "CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG";
public static final String CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED = "CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED";
public static final String VALUESET_CIRCULAR_REFERENCE = "VALUESET_CIRCULAR_REFERENCE";
public static final String VALUESET_SUPPLEMENT_MISSING = "VALUESET_SUPPLEMENT_MISSING";
}

View File

@ -7,4 +7,8 @@ public abstract class JsonPrimitive extends JsonElement {
public String toJson() {
return getValue();
}
public boolean asBoolean() {
return "true".equals(getValue());
}
}

View File

@ -925,8 +925,9 @@ UNKNOWN_CODESYSTEM = The CodeSystem {0} is unknown
UNKNOWN_CODESYSTEM_VERSION = The CodeSystem {0} version {1} is unknown. Valid versions: {2}
UNABLE_TO_INFER_CODESYSTEM = The System URI could not be determined for the code {0} in the ValueSet {1}
VALUESET_TOO_COSTLY = The value set {0} has too many codes to display ({1})
NO_VALID_DISPLAY_FOUND_one = No valid Display Names found for {0}#{1} in the language {3}
NO_VALID_DISPLAY_FOUND_other = No valid Display Names found for {0}#{1} in the languages {3}
VALUESET_TOO_COSTLY_TIME = The value set {0} took too long to process (>{1}sec)
NO_VALID_DISPLAY_FOUND_one = No valid Display Names found for {1}#{2} in the language {4}
NO_VALID_DISPLAY_FOUND_other = No valid Display Names found for {1}#{2} in the languages {4}
SD_NO_CONTEXT_WHEN_NOT_EXTENSION = The type is {0} so an extension context should not be specified
SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION = The type is {0} so an extension context invariants should not be specified
SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = Review the extension type: extensions should not have a context of {0} unless it''s really intended that they can be used anywhere
@ -1009,11 +1010,16 @@ FHIRPATH_OFTYPE_IMPOSSIBLE = The type specified in ofType is {1} which is not a
ED_SEARCH_EXPRESSION_ERROR = Error in search expression ''{0}'': {1}
SD_EXTENSION_URL_MISMATCH = The fixed value for the extension URL is {1} which doesn''t match the canonical URL {0}
SD_EXTENSION_URL_MISSING = The value of Extension.url is not fixed to the extension URL {0}
MSG_DEPRECATED = Reference to deprecated item {0}
MSG_DEPRECATED = Reference to deprecated item {0}
MSG_WITHDRAWN = Reference to withdrawn item {0}
MSG_RETIRED = Reference to retired item {0}
MSG_RETIRED = Reference to retired item {0}
MSG_EXPERIMENTAL = Reference to experimental item {0}
MSG_DRAFT = Reference to draft item {0}
MSG_DEPRECATED_SRC = Reference to deprecated item {0} from {1}
MSG_WITHDRAWN_SRC = Reference to withdrawn item {0} from {1}
MSG_RETIRED_SRC = Reference to retired item {0} from {1}
MSG_EXPERIMENTAL_SRC = Reference to experimental item {0} from {1}
MSG_DRAFT_SRC = Reference to draft item {0} from {1}
INACTIVE_CODE_WARNING = The code ''{0}'' is valid but is not active
SD_ED_TYPE_PROFILE_WRONG_TYPE_one = The type {0} is not in the list of allowed type {1} in the profile {2}
SD_ED_TYPE_PROFILE_WRONG_TYPE_other = The type {0} is not in the list of allowed types {1} in the profile {2}
@ -1031,5 +1037,7 @@ CODESYSTEM_CS_COUNT_FRAGMENT_WRONG = The code system is a fragment/example, but
CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO = The code system has no content, but the exceeds the stated total number is 0 concepts - check that this isn't a complete code system that has no concepts, or update/remove the stated count
CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG = The code system supplement states the total number of concepts as {1}, but this is different to the underlying code system that states a value of {0}
CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED = The code system says it has no content present, but concepts are found
VALUESET_CIRCULAR_REFERENCE = Found a circularity pointing to {0} processing ValueSet with pathway {1}
VALUESET_SUPPLEMENT_MISSING_one = Required supplement not found: {1}
VALUESET_SUPPLEMENT_MISSING_other = Required supplements not found: {1}

View File

@ -838,3 +838,6 @@ Display_Name_WS_for__should_be_one_of__instead_of_other =
SD_ED_TYPE_PROFILE_WRONG_TYPE_one =
SD_ED_TYPE_PROFILE_WRONG_TYPE_many =
SD_ED_TYPE_PROFILE_WRONG_TYPE_other =
VALUESET_SUPPLEMENT_MISSING_one =
VALUESET_SUPPLEMENT_MISSING_many =
VALUESET_SUPPLEMENT_MISSING_other =

View File

@ -1,9 +1,13 @@
package org.hl7.fhir.validation.cli.tasks;
import java.io.IOException;
import java.io.PrintStream;
import org.hl7.fhir.utilities.SystemExitManager;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.json.JsonException;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.utils.Params;
import org.hl7.fhir.validation.special.TxTester;
@ -41,8 +45,17 @@ public class TxTestsTask extends StandaloneTask{
final String version = Params.getParam(args, Params.VERSION);
final String tx = Params.getParam(args, Params.TERMINOLOGY);
final String filter = Params.getParam(args, Params.FILTER);
boolean ok = new TxTester(new TxTester.InternalTxLoader(source, output), tx, false).setOutput(output).execute(version, cliContext.getModeParams(), filter);
final String externals = Params.getParam(args, Params.EXTERNALS);
boolean ok = new TxTester(new TxTester.InternalTxLoader(source, output), tx, false, loadExternals(externals)).setOutput(output).execute(version, cliContext.getModeParams(), filter);
SystemExitManager.setError(ok ? 1 : 0);
SystemExitManager.finish();
}
private JsonObject loadExternals(String externals) throws JsonException, IOException {
if (externals == null) {
return null;
} else {
return JsonParser.parseObjectFromFile(externals);
}
}
}

View File

@ -99,6 +99,7 @@ public class Params {
public static final String SOURCE = "-source";
public static final String INPUT = "-input";
public static final String FILTER = "-filter";
public static final String EXTERNALS = "-externals";
public static final String MODE = "-mode";
private static final String FHIR_SETTINGS_PARAM = "-fhir-settings";
private static final String WATCH_MODE_PARAM = "-watch-mode";

View File

@ -55,17 +55,19 @@ public class TxTester {
private String output;
private ITerminologyClient tx;
private boolean tight;
private JsonObject externals;
public TxTester(ITxTesterLoader loader, String server, boolean tight) {
public TxTester(ITxTesterLoader loader, String server, boolean tight, JsonObject externals) {
super();
this.server = server;
this.loader = loader;
this.tight = tight;
this.externals = externals;
}
public static void main(String[] args) throws Exception {
new TxTester(new InternalTxLoader(args[0]), args[1], "true".equals(args[2])).execute(args[2], new ArrayList<>(), args[3]);
new TxTester(new InternalTxLoader(args[0]), args[1], "true".equals(args[2]), args.length == 5 ? JsonParser.parseObjectFromFile(args[4]) : null).execute(args[2], new ArrayList<>(), args[3]);
}
public boolean execute(String version, List<String> modes, String filter) throws IOException, URISyntaxException {
@ -77,6 +79,7 @@ public class TxTester {
System.out.println(" Source for tests: "+loader.describe());
System.out.println(" Output Directory: "+output);
System.out.println(" Term Service Url: "+server);
System.out.println(" External Strings: "+(externals != null));
System.out.println(" Test Exec Modes: "+modes.toString());
if (version != null) {
System.out.println(" Tx FHIR Version: "+version);
@ -185,12 +188,13 @@ public class TxTester {
if (fo.exists()) {
fo.delete();
}
JsonObject ext = externals == null ? null : externals.getJsonObject(fn);
String msg = null;
if (test.asString("operation").equals("expand")) {
msg = expand(tx, setup, req, resp, fp, profile);
msg = expand(tx, setup, req, resp, fp, profile, ext);
} else if (test.asString("operation").equals("validate-code")) {
msg = validate(tx, setup, req, resp, fp, profile);
msg = validate(tx, setup, req, resp, fp, profile, ext);
} else {
throw new Exception("Unknown Operation "+test.asString("operation"));
}
@ -239,7 +243,7 @@ public class TxTester {
return new URI(server).getHost();
}
private String expand(ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, Parameters profile) throws IOException {
private String expand(ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, Parameters profile, JsonObject ext) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
@ -255,7 +259,7 @@ public class TxTester {
TxTesterScrubbers.scrubOO(oo, tight);
vsj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj);
String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
TextFile.stringToFile(vsj, fp);
@ -263,7 +267,7 @@ public class TxTester {
return diff;
}
private String validate(ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, Parameters profile) throws IOException {
private String validate(ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, Parameters profile, JsonObject ext) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
@ -279,7 +283,7 @@ public class TxTester {
oo.setText(null);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj);
String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
TextFile.stringToFile(pj, fp);

View File

@ -14,6 +14,7 @@ import java.util.Map;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.NotImplementedException;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
@ -32,6 +33,8 @@ import org.hl7.fhir.r5.comparison.StructureDefinitionComparer;
import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison;
import org.hl7.fhir.r5.comparison.ValueSetComparer;
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r5.conformance.profile.BindingResolution;
import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.context.BaseWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext;
@ -45,6 +48,7 @@ import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.renderers.CodeSystemRenderer;
import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer;
import org.hl7.fhir.r5.renderers.ValueSetRenderer;
@ -150,11 +154,14 @@ public class ComparisonTests {
ComparisonSession session = new ComparisonSession(context, context, "Comparison Tests", null, null);
if (content.has("version")) {
session.setForVersion(content.getJsonObject("version").asString("stated"));
session.setAnnotate(true);
}
RenderingContext lrc = new RenderingContext(context, new MarkDownProcessor(Dialect.COMMON_MARK), null, "http://hl7.org/fhir", "", "en", ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER);
lrc.setDestDir(Utilities.path("[tmp]", "comparison"));
lrc.setPkp(new TestProfileKnowledgeProvider(context));
if (content.has("version")) {
lrc.setChangeVersion(content.getJsonObject("version").asString("stated"));
}
if (left instanceof CodeSystem && right instanceof CodeSystem) {
CodeSystemComparer cs = new CodeSystemComparer(session);
@ -199,6 +206,7 @@ public class ComparisonTests {
// String xml3 = new XhtmlComposer(true).compose(cs.renderExpansion(csc, "", ""));
TextFile.stringToFile(HEADER + hd("Messages") + xmle + BREAK + hd("Metadata") + xml1 + BREAK + hd("Structure") + xml2 + FOOTER, Utilities.path("[tmp]", "comparison", name + ".html"));
checkOutcomes(csc.getMessages(), content);
lrc.setStructureMode(StructureDefinitionRendererMode.DATA_DICT);
new StructureDefinitionRenderer(lrc).render(right);
@ -322,4 +330,83 @@ public class ComparisonTests {
Assertions.assertEquals(output.asInteger("infoCount"), hc, "Expected " + Integer.toString(output.asInteger("infoCount")) + " hints, but found " + Integer.toString(hc) + ".");
}
public class TestProfileKnowledgeProvider implements ProfileKnowledgeProvider {
private IWorkerContext context;
public TestProfileKnowledgeProvider(IWorkerContext context) {
this.context = context;
}
@Override
public boolean isDatatype(String typeSimple) {
throw new NotImplementedException();
}
@Override
public boolean isPrimitiveType(String typeSimple) {
throw new NotImplementedException();
}
@Override
public boolean isResource(String typeSimple) {
throw new NotImplementedException();
}
@Override
public boolean hasLinkFor(String typeSimple) {
return getLinkFor(null, typeSimple) != null;
}
@Override
public String getLinkFor(String corePath, String typeSimple) {
StructureDefinition sd = context.fetchTypeDefinition(typeSimple);
if (sd != null) {
return sd.getWebPath();
}
return null;
}
@Override
public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) throws FHIRException {
ValueSet vs = context.fetchResource(ValueSet.class, binding.getValueSet());
if (vs != null) {
return new BindingResolution(vs.present(), vs.getWebPath());
} else {
return new BindingResolution(binding.getValueSet(), null);
}
}
@Override
public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException {
ValueSet vs = context.fetchResource(ValueSet.class, url);
if (vs != null) {
if (vs.hasWebPath()) {
return new BindingResolution(vs.present(), vs.getWebPath());
} else {
return new BindingResolution(vs.present(), "valueset-"+vs.getIdBase()+".html");
}
}
throw new NotImplementedException();
}
@Override
public String getLinkForProfile(StructureDefinition profile, String url) {
if ("http://hl7.org/fhir/StructureDefinition/Composition".equals(url)) {
return "http://hl7.org/fhir/composition.html|TestComposition";
}
throw new NotImplementedException();
}
@Override
public boolean prependLinks() {
return false;
}
@Override
public String getLinkForUrl(String corePath, String s) {
throw new NotImplementedException();
}
}
}

View File

@ -120,7 +120,7 @@ public class StructureMappingTests {
fail(e.getMessage());
}
if (output.endsWith("json")) {
msg = CompareUtilities.checkJsonSrcIsSame(s.toString(), outputJson);
msg = CompareUtilities.checkJsonSrcIsSame(s.toString(), outputJson, null);
} else {
TextFile.bytesToFile(s.toByteArray(), fileOutputRes);
TextFile.bytesToFile(outputJson.getBytes(), fileOutputResOrig);

View File

@ -55,6 +55,7 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader {
public static Iterable<Object[]> data() throws IOException {
String contents = TestingUtilities.loadTestResource("tx", "test-cases.json");
externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(TestingUtilities.loadTestResource("tx", "messages-tx.fhir.org.json"));
Map<String, JsonObjectPair> examples = new HashMap<String, JsonObjectPair>();
manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents);
@ -78,6 +79,7 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader {
}
private static org.hl7.fhir.utilities.json.model.JsonObject manifest;
private static org.hl7.fhir.utilities.json.model.JsonObject externals;
private JsonObjectPair setup;
private String version = "5.0.0";
private static TxTester tester;
@ -92,7 +94,7 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader {
public void test() throws Exception {
if (SERVER != null) {
if (tester == null) {
tester = new TxTester(this, SERVER, true);
tester = new TxTester(this, SERVER, true, externals);
}
String err = tester.executeTest(setup.suite, setup.test, modes);
Assertions.assertTrue(err == null, err);

View File

@ -76,7 +76,9 @@ public class TerminologyServiceTests {
public static Iterable<Object[]> data() throws IOException {
String contents = TestingUtilities.loadTestResource("tx", "test-cases.json");
String externalSource = TestingUtilities.loadTestResource("tx", "messages-tx.fhir.org.json");
externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(externalSource);
Map<String, JsonObjectPair> examples = new HashMap<String, JsonObjectPair>();
manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents);
for (org.hl7.fhir.utilities.json.model.JsonObject suite : manifest.getJsonObjects("suites")) {
@ -99,6 +101,7 @@ public class TerminologyServiceTests {
}
private static org.hl7.fhir.utilities.json.model.JsonObject manifest;
private static org.hl7.fhir.utilities.json.model.JsonObject externals;
private JsonObjectPair setup;
private String version;
private String name;
@ -128,6 +131,7 @@ public class TerminologyServiceTests {
String fn = setup.test.asString("response");
String resp = TestingUtilities.loadTestResource("tx", fn);
String fp = Utilities.path("[tmp]", "tx", fn);
JsonObject ext = externals == null ? null : externals.getJsonObject(fn);
File fo = new File(fp);
if (fo.exists()) {
fo.delete();
@ -138,15 +142,15 @@ public class TerminologyServiceTests {
engine.getContext().setExpansionProfile((org.hl7.fhir.r5.model.Parameters) loadResource("parameters-default.json"));
}
if (setup.test.asString("operation").equals("expand")) {
expand(engine, req, resp, fp);
expand(engine, req, resp, fp, ext);
} else if (setup.test.asString("operation").equals("validate-code")) {
validate(engine, setup.test.asString("name"), req, resp, fp);
validate(engine, setup.test.asString("name"), req, resp, fp, ext);
} else {
Assertions.fail("Unknown Operation "+setup.test.asString("operation"));
}
}
private void expand(ValidationEngine engine, Resource req, String resp, String fp) throws IOException {
private void expand(ValidationEngine engine, Resource req, String resp, String fp, JsonObject ext) throws IOException {
org.hl7.fhir.r5.model.Parameters p = ( org.hl7.fhir.r5.model.Parameters) req;
ValueSet vs = engine.getContext().fetchResource(ValueSet.class, p.getParameterValue("url").primitiveValue());
boolean hierarchical = p.hasParameter("excludeNested") ? p.getParameterBool("excludeNested") == false : true;
@ -162,7 +166,7 @@ public class TerminologyServiceTests {
TxTesterSorters.sortValueSet(vse.getValueset());
TxTesterScrubbers.scrubVS(vse.getValueset(), false);
String vsj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(vse.getValueset());
String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj);
String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
TextFile.stringToFile(vsj, fp);
@ -207,7 +211,7 @@ public class TerminologyServiceTests {
TxTesterScrubbers.scrubOO(oo, false);
String ooj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
String diff = CompareUtilities.checkJsonSrcIsSame(resp, ooj);
String diff = CompareUtilities.checkJsonSrcIsSame(resp, ooj, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
TextFile.stringToFile(ooj, fp);
@ -225,7 +229,7 @@ public class TerminologyServiceTests {
}
}
private void validate(ValidationEngine engine, String name, Resource req, String resp, String fp) throws JsonSyntaxException, FileNotFoundException, IOException {
private void validate(ValidationEngine engine, String name, Resource req, String resp, String fp, JsonObject ext) throws JsonSyntaxException, FileNotFoundException, IOException {
org.hl7.fhir.r5.model.Parameters p = (org.hl7.fhir.r5.model.Parameters) req;
ValueSet vs = null;
if (p.hasParameter("valueSetVersion")) {
@ -299,7 +303,7 @@ public class TerminologyServiceTests {
TxTesterScrubbers.scrubParams(res);
String pj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(res);
String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj);
String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
TextFile.stringToFile(pj, fp);