work on API comparison
This commit is contained in:
parent
239084ed0f
commit
34a3dfb43e
|
@ -0,0 +1,682 @@
|
|||
package org.hl7.fhir.r5.conformance;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
|
||||
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.RestfulCapabilityMode;
|
||||
import org.hl7.fhir.r5.model.CodeableConcept;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.Element;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.MarkDownProcessor;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlDocument;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
|
||||
|
||||
public class CapabilityStatementUtilities {
|
||||
|
||||
private IWorkerContext context;
|
||||
private String selfName;
|
||||
private String otherName;
|
||||
private List<ValidationMessage> output;
|
||||
private XhtmlDocument html;
|
||||
private MarkDownProcessor markdown = new MarkDownProcessor(Dialect.COMMON_MARK);
|
||||
private String folder;
|
||||
// private String css;
|
||||
|
||||
public CapabilityStatementUtilities(SimpleWorkerContext context, String folder) throws IOException {
|
||||
super();
|
||||
this.context = context;
|
||||
this.folder = folder;
|
||||
String f = Utilities.path(folder, "comparison.zip");
|
||||
download("http://fhir.org/archive/comparison.zip", f);
|
||||
unzip(f, folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of the buffer to read/write data
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
/**
|
||||
* Extracts a zip file specified by the zipFilePath to a directory specified by
|
||||
* destDirectory (will be created if does not exists)
|
||||
* @param zipFilePath
|
||||
* @param destDirectory
|
||||
* @throws IOException
|
||||
*/
|
||||
public void unzip(String zipFilePath, String destDirectory) throws IOException {
|
||||
File destDir = new File(destDirectory);
|
||||
if (!destDir.exists()) {
|
||||
destDir.mkdir();
|
||||
}
|
||||
ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
|
||||
ZipEntry entry = zipIn.getNextEntry();
|
||||
// iterates over entries in the zip file
|
||||
while (entry != null) {
|
||||
String filePath = destDirectory + File.separator + entry.getName();
|
||||
if (!entry.isDirectory()) {
|
||||
// if the entry is a file, extracts it
|
||||
extractFile(zipIn, filePath);
|
||||
} else {
|
||||
// if the entry is a directory, make the directory
|
||||
File dir = new File(filePath);
|
||||
dir.mkdir();
|
||||
}
|
||||
zipIn.closeEntry();
|
||||
entry = zipIn.getNextEntry();
|
||||
}
|
||||
zipIn.close();
|
||||
}
|
||||
/**
|
||||
* Extracts a zip entry (file entry)
|
||||
* @param zipIn
|
||||
* @param filePath
|
||||
* @throws IOException
|
||||
*/
|
||||
private void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
|
||||
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
|
||||
byte[] bytesIn = new byte[BUFFER_SIZE];
|
||||
int read = 0;
|
||||
while ((read = zipIn.read(bytesIn)) != -1) {
|
||||
bos.write(bytesIn, 0, read);
|
||||
}
|
||||
bos.close();
|
||||
}
|
||||
|
||||
public void saveToFile() throws IOException {
|
||||
String s = new XhtmlComposer(true, true).compose(html);
|
||||
TextFile.stringToFile(s, Utilities.path(folder, "index.html"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares 2 capability statements to see if self is a subset of other
|
||||
*
|
||||
* e.g. is a system implementing "+otherName+" also an implementation of self?
|
||||
*
|
||||
* the output is a series of messages identifying ways in which it is not,
|
||||
* or warning messages where the algorithm is unable to determine the
|
||||
* relationship
|
||||
*
|
||||
* Note that there are aspects of this question that are not computable.
|
||||
* the
|
||||
*
|
||||
* @param self
|
||||
* @param other
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws FHIRFormatError
|
||||
* @throws DefinitionException
|
||||
*/
|
||||
public List<ValidationMessage> isCompatible(String selfName, String otherName, CapabilityStatement self, CapabilityStatement other) throws DefinitionException, FHIRFormatError, IOException {
|
||||
this.selfName = selfName;
|
||||
this.otherName = otherName;
|
||||
this.output = new ArrayList<>();
|
||||
XhtmlNode x = startHtml();
|
||||
|
||||
information(x, IssueType.INVARIANT, self.getUrl(), "Comparing "+selfName+" to "+otherName+", to see if a server that implements "+otherName+" also implements "+selfName+"");
|
||||
information(x, IssueType.INVARIANT, self.getUrl(), " "+selfName+": "+self.getUrl()+"|"+self.getVersion());
|
||||
information(x, IssueType.INVARIANT, self.getUrl(), " "+otherName+": "+other.getUrl()+"|"+other.getVersion());
|
||||
|
||||
if (self.getRest().size() != 1 || self.getRestFirstRep().getMode() != RestfulCapabilityMode.SERVER)
|
||||
fatal(x, IssueType.INVARIANT, self.getUrl()+"#rest", "The CapabilityStatement Comparison tool can only compare CapabilityStatements with a single server component");
|
||||
if (other.getRest().size() != 1 || other.getRestFirstRep().getMode() != RestfulCapabilityMode.SERVER)
|
||||
fatal(x, IssueType.INVARIANT, other.getUrl()+"#rest", "The CapabilityStatement Comparison tool can only compare CapabilityStatements with a single server component");
|
||||
|
||||
if (self.getRest().size() == 1 && other.getRest().size() == 1) {
|
||||
XhtmlNode tbl = startTable(x, self, other);
|
||||
compareRest(tbl, self.getUrl(), self.getRest().get(0), other.getRest().get(0));
|
||||
}
|
||||
if (folder != null)
|
||||
saveToFile();
|
||||
return output;
|
||||
}
|
||||
|
||||
private void compareRest(XhtmlNode tbl, String path, CapabilityStatementRestComponent self, CapabilityStatementRestComponent other) throws DefinitionException, FHIRFormatError, IOException {
|
||||
compareSecurity(tbl, path, self, other);
|
||||
|
||||
// check resources
|
||||
List<CapabilityStatementRestResourceComponent> ol = new ArrayList<>();
|
||||
List<CapabilityStatementRestResourceComponent> olr = new ArrayList<>();
|
||||
ol.addAll(other.getResource());
|
||||
for (CapabilityStatementRestResourceComponent r : self.getResource()) {
|
||||
CapabilityStatementRestResourceComponent o = null;
|
||||
for (CapabilityStatementRestResourceComponent t : ol) {
|
||||
if (t.getType().equals(r.getType())) {
|
||||
o = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
XhtmlNode tr = tbl.tr();
|
||||
tr.style("background-color: #dddddd");
|
||||
tr.td().b().addText(r.getType());
|
||||
tr.td().tx("Present");
|
||||
|
||||
|
||||
if (o == null) {
|
||||
XhtmlNode p = tr.td().para("Absent");
|
||||
XhtmlNode td = tr.td();
|
||||
String s = getConfStatus(r);
|
||||
if (Utilities.existsInList(s, "SHALL", "SHOULD")) {
|
||||
error(td, IssueType.NOTFOUND, path+".resource.where(type = '"+r.getType()+"')", selfName+" specifies the resource "+r.getType()+" as "+s+" but "+otherName+" does not cover it");
|
||||
p.style("background-color: #ffe6e6; border: 1px solid #ff1a1a; margin-width: 10px");
|
||||
}
|
||||
|
||||
tr = tbl.tr();
|
||||
tr.td().para().tx(XhtmlNode.NBSP+" - Conformance");
|
||||
genConf(tr.td().para(), r, null);
|
||||
tr.td().style("background-color: #eeeeee");
|
||||
tr.td();
|
||||
|
||||
tr = tbl.tr();
|
||||
tr.td().para().tx(XhtmlNode.NBSP+" - Profile");
|
||||
genProfile(tr.td().para(), r, null);
|
||||
tr.td().style("background-color: #eeeeee");
|
||||
tr.td();
|
||||
|
||||
tr = tbl.tr();
|
||||
tr.td().para().tx(XhtmlNode.NBSP+" - Interactions");
|
||||
genInt(tr.td(), r, null, false);
|
||||
tr.td().style("background-color: #eeeeee");
|
||||
tr.td();
|
||||
|
||||
tr = tbl.tr();
|
||||
tr.td().para().tx(XhtmlNode.NBSP+" - Search Parameters");
|
||||
genSP(tr.td(), r, null, false);
|
||||
tr.td().style("background-color: #eeeeee");
|
||||
tr.td();
|
||||
|
||||
} else {
|
||||
olr.add(o);
|
||||
tr.td().tx("Present");
|
||||
tr.td().nbsp();
|
||||
compareResource(path+".resource.where(type = '"+r.getType()+"')", r, o, tbl);
|
||||
}
|
||||
}
|
||||
for (CapabilityStatementRestResourceComponent t : ol) {
|
||||
XhtmlNode tr = tbl.tr();
|
||||
if (!olr.contains(t)) {
|
||||
tr.td().addText(t.getType());
|
||||
XhtmlNode td = tr.td();
|
||||
td.style("background-color: #eeeeee").para("Absent");
|
||||
td = tr.td();
|
||||
genConf(td, t, null);
|
||||
genProfile(td, t, null);
|
||||
genInt(td, t, null, false);
|
||||
genSP(td, t, null, false);
|
||||
if (isProhibited(t)) {
|
||||
error(td, IssueType.INVARIANT, path+".resource", selfName+" does not specify the resource "+t.getType()+" but "+otherName+" prohibits it");
|
||||
}
|
||||
}
|
||||
}
|
||||
// check interactions
|
||||
// check search parameters
|
||||
// check operation
|
||||
// check compartments
|
||||
}
|
||||
|
||||
private void genConf(XhtmlNode x, CapabilityStatementRestResourceComponent r, CapabilityStatementRestResourceComponent other) {
|
||||
String s = getConfStatus(r);
|
||||
String so = other != null ? getConfStatus(other) : null;
|
||||
x.add(same(s == null ? "(not specified)" : s, (s == null && so == null) || (s != null && s.equals(so))));
|
||||
}
|
||||
|
||||
private void genProfile(XhtmlNode x, CapabilityStatementRestResourceComponent r, CapabilityStatementRestResourceComponent other) {
|
||||
String s = getProfile(r);
|
||||
if (s == null)
|
||||
s = "(not specified)";
|
||||
String so = other == null ? null : getProfile(other) == null ? getProfile(other) : "(not specified)";
|
||||
x.add(same(s, (s == null && so == null) || (s != null && s.equals(so))));
|
||||
}
|
||||
|
||||
private String getProfile(CapabilityStatementRestResourceComponent r) {
|
||||
if (r.hasSupportedProfile() && r.getSupportedProfile().size() == 1)
|
||||
return r.getSupportedProfile().get(0).asStringValue();
|
||||
return r.getProfile();
|
||||
}
|
||||
|
||||
private void genInt(XhtmlNode td, CapabilityStatementRestResourceComponent r, CapabilityStatementRestResourceComponent other, boolean errorIfNoMatch) {
|
||||
boolean first = true;
|
||||
for (ResourceInteractionComponent i : r.getInteraction()) {
|
||||
if (first) first = false; else td.tx(", ");
|
||||
if (exists(other, i)) {
|
||||
td.code().span("background-color: #bbff99; border: 1px solid #44cc00; margin-width: 10px", null).tx(i.getCode().toCode());
|
||||
} else if (errorIfNoMatch){
|
||||
td.code().span("background-color: #ffe6e6; border: 1px solid #ff1a1a; margin-width: 10px", null).tx(i.getCode().toCode());
|
||||
} else {
|
||||
td.code(i.getCode().toCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean exists(CapabilityStatementRestResourceComponent other, ResourceInteractionComponent i) {
|
||||
if (other == null)
|
||||
return false;
|
||||
for (ResourceInteractionComponent t : other.getInteraction())
|
||||
if (t.getCode().equals(i.getCode()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void genSP(XhtmlNode td, CapabilityStatementRestResourceComponent r, CapabilityStatementRestResourceComponent other, boolean errorIfNoMatch) {
|
||||
boolean first = true;
|
||||
for (CapabilityStatementRestResourceSearchParamComponent i : r.getSearchParam()) {
|
||||
if (first) first = false; else td.tx(", ");
|
||||
if (exists(other, i)) {
|
||||
td.code().span("background-color: #bbff99; border: 1px solid #44cc00; margin-width: 10px", null).tx(i.getName());
|
||||
} else if (errorIfNoMatch){
|
||||
td.code().span("background-color: #ffe6e6; border: 1px solid #ff1a1a; margin-width: 10px", null).tx(i.getName());
|
||||
} else {
|
||||
td.code(i.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean exists(CapabilityStatementRestResourceComponent other, CapabilityStatementRestResourceSearchParamComponent i) {
|
||||
if (other == null)
|
||||
return false;
|
||||
for (CapabilityStatementRestResourceSearchParamComponent t : other.getSearchParam())
|
||||
if (t.getName().equals(i.getName()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void compareSecurity(XhtmlNode tbl, String path, CapabilityStatementRestComponent self, CapabilityStatementRestComponent other) {
|
||||
XhtmlNode tr = tbl.tr();
|
||||
tr.td().b().addText("Security");
|
||||
tr.td().para(gen(self.getSecurity()));
|
||||
tr.td().para(gen(other.getSecurity()));
|
||||
XhtmlNode td = tr.td();
|
||||
|
||||
if (self.hasSecurity() && !other.hasSecurity())
|
||||
error(td, IssueType.CONFLICT, path+".security", selfName+" specifies some security requirements ("+gen(self.getSecurity())+") but "+otherName+" doesn't");
|
||||
else if (!self.hasSecurity() && other.hasSecurity())
|
||||
error(td, IssueType.CONFLICT, path+".security", selfName+" does not specify security requirements but "+otherName+" does ("+gen(self.getSecurity())+")");
|
||||
else if (self.hasSecurity() && other.hasSecurity())
|
||||
compareSecurity(td, path+".security", self.getSecurity(), other.getSecurity());
|
||||
}
|
||||
|
||||
private void compareResource(String path, CapabilityStatementRestResourceComponent self, CapabilityStatementRestResourceComponent other, XhtmlNode tbl) throws DefinitionException, FHIRFormatError, IOException {
|
||||
XhtmlNode tr = tbl.tr();
|
||||
tr.td().para().tx(XhtmlNode.NBSP+" - Conformance");
|
||||
genConf(tr.td(), self, other);
|
||||
genConf(tr.td(), other, self);
|
||||
tr.td().nbsp();
|
||||
|
||||
tr = tbl.tr();
|
||||
tr.td().para().tx(XhtmlNode.NBSP+" - Profile");
|
||||
genProfile(tr.td(), self, other);
|
||||
genProfile(tr.td(), other, self);
|
||||
compareProfiles(tr.td(), path, getProfile(self), getProfile(other), self.getType());
|
||||
|
||||
// compare the interactions
|
||||
compareResourceInteractions(path, self, other, tbl);
|
||||
compareResourceSearchParams(path, self, other, tbl);
|
||||
// compare the search parameters
|
||||
// compare the operations
|
||||
|
||||
// compare the profile?
|
||||
|
||||
}
|
||||
|
||||
private void compareProfiles(XhtmlNode td, String path, String urlL, String urlR, String type) throws DefinitionException, FHIRFormatError, IOException {
|
||||
if (urlL == null) {
|
||||
urlL = "http://hl7.org/fhir/StructureDefinition/"+type;
|
||||
}
|
||||
if (urlR == null) {
|
||||
urlR = "http://hl7.org/fhir/StructureDefinition/"+type;
|
||||
}
|
||||
StructureDefinition sdL = context.fetchResource(StructureDefinition.class, urlL);
|
||||
StructureDefinition sdR = context.fetchResource(StructureDefinition.class, urlR);
|
||||
if (sdL == null)
|
||||
error(td, IssueType.NOTFOUND, path, "Unable to resolve "+urlL);
|
||||
if (sdR == null)
|
||||
error(td, IssueType.NOTFOUND, path, "Unable to resolve "+urlR);
|
||||
|
||||
if (sdL != null && sdR != null && sdL != sdR) {
|
||||
// ok they are different...
|
||||
if (sdR.getUrl().equals(sdL.getBaseDefinition())) {
|
||||
information(td, null, path, "The profile specified by "+selfName+" is inherited from the profile specified by "+otherName);
|
||||
} else if (folder != null) {
|
||||
try {
|
||||
ProfileComparer pc = new ProfileComparer(context);
|
||||
pc.setId("api-ep."+type);
|
||||
pc.setTitle("Comparison - "+selfName+" vs "+otherName);
|
||||
pc.setLeftName(selfName+": "+sdL.present());
|
||||
pc.setLeftLink(sdL.getUserString("path"));
|
||||
pc.setRightName(otherName+": "+sdR.present());
|
||||
pc.setRightLink(sdR.getUserString("path"));
|
||||
pc.compareProfiles(sdL, sdR);
|
||||
pc.generate(folder);
|
||||
td.ah(pc.getId()+".html").tx("Comparison...");
|
||||
td.tx(pc.getErrCount()+" "+Utilities.pluralize("error", pc.getErrCount()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
error(td, IssueType.EXCEPTION, path, "Error comparing profiles: "+e.getMessage());
|
||||
}
|
||||
} else {
|
||||
information(td, null, path, "Use the validator to compare the profiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void compareResourceInteractions(String path, CapabilityStatementRestResourceComponent self, CapabilityStatementRestResourceComponent other, XhtmlNode tbl) {
|
||||
XhtmlNode tr = tbl.tr();
|
||||
tr.td().para().tx(XhtmlNode.NBSP+" - Interactions");
|
||||
genInt(tr.td(), self, other, true);
|
||||
genInt(tr.td(), other, self, false);
|
||||
XhtmlNode td = tr.td();
|
||||
List<ResourceInteractionComponent> ol = new ArrayList<>();
|
||||
List<ResourceInteractionComponent> olr = new ArrayList<>();
|
||||
ol.addAll(other.getInteraction());
|
||||
for (ResourceInteractionComponent r : self.getInteraction()) {
|
||||
ResourceInteractionComponent o = null;
|
||||
for (ResourceInteractionComponent t : ol) {
|
||||
if (t.getCode().equals(r.getCode())) {
|
||||
o = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (o == null) {
|
||||
error(td, IssueType.NOTFOUND, path+".interaction.where(code = '"+r.getCode()+"')", selfName+" specifies the interaction "+r.getCode()+" but "+otherName+" does not");
|
||||
} else {
|
||||
olr.add(o);
|
||||
}
|
||||
}
|
||||
for (ResourceInteractionComponent t : ol) {
|
||||
if (!olr.contains(t) && isProhibited(t))
|
||||
error(td, IssueType.CONFLICT, path+".interaction", selfName+" does not specify the interaction "+t.getCode()+" but "+otherName+" prohibits it");
|
||||
}
|
||||
}
|
||||
|
||||
private void compareResourceSearchParams(String path, CapabilityStatementRestResourceComponent self, CapabilityStatementRestResourceComponent other, XhtmlNode tbl) {
|
||||
XhtmlNode tr = tbl.tr();
|
||||
tr.td().para().tx(XhtmlNode.NBSP+" - Search Params");
|
||||
genSP(tr.td(), self, other, true);
|
||||
genSP(tr.td(), other, self, false);
|
||||
XhtmlNode td = tr.td();
|
||||
|
||||
List<CapabilityStatementRestResourceSearchParamComponent> ol = new ArrayList<>();
|
||||
List<CapabilityStatementRestResourceSearchParamComponent> olr = new ArrayList<>();
|
||||
ol.addAll(other.getSearchParam());
|
||||
for (CapabilityStatementRestResourceSearchParamComponent r : self.getSearchParam()) {
|
||||
CapabilityStatementRestResourceSearchParamComponent o = null;
|
||||
for (CapabilityStatementRestResourceSearchParamComponent t : ol) {
|
||||
if (t.getName().equals(r.getName())) {
|
||||
o = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (o == null) {
|
||||
error(td, IssueType.NOTFOUND, path+".searchParam.where(name = '"+r.getName()+"')", selfName+" specifies the search parameter "+r.getName()+" but "+otherName+" does not");
|
||||
} else {
|
||||
olr.add(o);
|
||||
}
|
||||
}
|
||||
for (CapabilityStatementRestResourceSearchParamComponent t : ol) {
|
||||
if (!olr.contains(t) && isProhibited(t))
|
||||
error(td, IssueType.CONFLICT, path+"", selfName+" does not specify the search parameter "+t.getName()+" but "+otherName+" prohibits it");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getConfStatus(Element t) {
|
||||
return t.hasExtension("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation") ?
|
||||
t.getExtensionString("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation") : null;
|
||||
}
|
||||
|
||||
private boolean isShouldOrShall(Element t) {
|
||||
return t.hasExtension("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation") &&
|
||||
("SHALL".equals(t.getExtensionString("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation")) || "SHOULD".equals(t.getExtensionString("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation")));
|
||||
}
|
||||
|
||||
private boolean isProhibited(Element t) {
|
||||
return t.hasExtension("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation") && "SHALL NOT".equals(t.getExtensionString("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation"));
|
||||
}
|
||||
|
||||
private void compareSecurity(XhtmlNode td, String path, CapabilityStatementRestSecurityComponent self, CapabilityStatementRestSecurityComponent other) {
|
||||
if (self.getCors() && !other.getCors())
|
||||
error(td, IssueType.CONFLICT, path+".security.cors", selfName+" specifies CORS but "+otherName+" doesn't");
|
||||
else if (!self.getCors() && other.getCors())
|
||||
error(td, IssueType.CONFLICT, path+".security.cors", selfName+" does not specify CORS but "+otherName+" does");
|
||||
|
||||
List<CodeableConcept> ol = new ArrayList<>();
|
||||
List<CodeableConcept> olr = new ArrayList<>();
|
||||
ol.addAll(other.getService());
|
||||
for (CodeableConcept cc : self.getService()) {
|
||||
CodeableConcept o = null;
|
||||
for (CodeableConcept t : ol) {
|
||||
if (isMatch(t, cc)) {
|
||||
o = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (o == null) {
|
||||
error(td, IssueType.CONFLICT, path+".security.cors", selfName+" specifies the security option "+gen(cc)+" but "+otherName+" does not");
|
||||
} else {
|
||||
olr.add(o);
|
||||
}
|
||||
}
|
||||
for (CodeableConcept cc : ol) {
|
||||
if (!olr.contains(cc))
|
||||
error(td, IssueType.CONFLICT, path+".security.cors", selfName+" does not specify the security option "+gen(cc)+" but "+otherName+" does");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMatch(CodeableConcept self, CodeableConcept other) {
|
||||
for (Coding s : self.getCoding())
|
||||
for (Coding o : other.getCoding())
|
||||
if (isMatch(s, o))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isMatch(Coding s, Coding o) {
|
||||
return s.hasCode() && s.getCode().equals(o.getCode()) && s.hasSystem() && s.getSystem().equals(o.getSystem());
|
||||
}
|
||||
|
||||
private String gen(CapabilityStatementRestSecurityComponent security) {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (CodeableConcept cc : security.getService())
|
||||
b.append(gen(cc));
|
||||
if (security.getCors())
|
||||
b.append("(CORS)");
|
||||
if (Utilities.noString(b.toString()))
|
||||
return "(none specified)";
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private String gen(CodeableConcept cc) {
|
||||
if (cc.hasText())
|
||||
return cc.getText();
|
||||
if (cc.hasCoding())
|
||||
return gen(cc.getCoding().get(0));
|
||||
return "??";
|
||||
}
|
||||
|
||||
private String gen(Coding coding) {
|
||||
if (coding.hasDisplay())
|
||||
return coding.getDisplay();
|
||||
if (coding.hasCode())
|
||||
return coding.getCode();
|
||||
return "???";
|
||||
}
|
||||
|
||||
|
||||
private XhtmlNode startHtml() {
|
||||
html = new XhtmlDocument();
|
||||
XhtmlNode doc = html.addTag("html");
|
||||
XhtmlNode head = doc.addTag("head");
|
||||
head.addTag("title").addText("Comparison of "+selfName+" to "+otherName);
|
||||
head.addTag("link").setAttribute("rel", "stylesheet").setAttribute("href", "fhir.css");
|
||||
XhtmlNode body = doc.addTag("body").style("background-color: white");
|
||||
|
||||
body.h1().addText("Comparison of "+selfName+" to "+otherName);
|
||||
return body;
|
||||
}
|
||||
|
||||
private void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException {
|
||||
if (text != null) {
|
||||
// 1. custom FHIR extensions
|
||||
while (text.contains("[[[")) {
|
||||
String left = text.substring(0, text.indexOf("[[["));
|
||||
String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]"));
|
||||
String right = text.substring(text.indexOf("]]]")+3);
|
||||
String url = link;
|
||||
String[] parts = link.split("\\#");
|
||||
StructureDefinition p = context.fetchResource(StructureDefinition.class, parts[0]);
|
||||
if (p == null)
|
||||
p = context.fetchTypeDefinition(parts[0]);
|
||||
if (p == null)
|
||||
p = context.fetchResource(StructureDefinition.class, link);
|
||||
if (p != null) {
|
||||
url = p.getUserString("path");
|
||||
if (url == null)
|
||||
url = p.getUserString("filename");
|
||||
} else
|
||||
throw new DefinitionException("Unable to resolve markdown link "+link);
|
||||
|
||||
text = left+"["+link+"]("+url+")"+right;
|
||||
}
|
||||
|
||||
// 2. markdown
|
||||
String s = markdown.process(Utilities.escapeXml(text), "narrative generator");
|
||||
XhtmlParser p = new XhtmlParser();
|
||||
XhtmlNode m;
|
||||
try {
|
||||
m = p.parse("<div>"+s+"</div>", "div");
|
||||
} catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
|
||||
throw new FHIRFormatError(e.getMessage(), e);
|
||||
}
|
||||
x.getChildNodes().addAll(m.getChildNodes());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private XhtmlNode startTable(XhtmlNode x, CapabilityStatement self, CapabilityStatement other) {
|
||||
XhtmlNode tbl = x.table("grid");
|
||||
XhtmlNode tr = tbl.tr();
|
||||
tr.td().b().nbsp();
|
||||
tr.td().b().addText(selfName);
|
||||
tr.td().b().addText(otherName);
|
||||
tr.td().b().addText("Comparison");
|
||||
return tbl;
|
||||
}
|
||||
|
||||
private void download(String address, String filename) throws IOException {
|
||||
URL url = new URL(address);
|
||||
URLConnection c = url.openConnection();
|
||||
InputStream s = c.getInputStream();
|
||||
FileOutputStream f = new FileOutputStream(filename);
|
||||
transfer(s, f, 1024);
|
||||
f.close();
|
||||
}
|
||||
|
||||
|
||||
public static void transfer(InputStream in, OutputStream out, int buffer) throws IOException {
|
||||
byte[] read = new byte[buffer]; // Your buffer size.
|
||||
while (0 < (buffer = in.read(read)))
|
||||
out.write(read, 0, buffer);
|
||||
}
|
||||
|
||||
private void fatal(XhtmlNode x, IssueType type, String path, String message) {
|
||||
output.add(new ValidationMessage(Source.ProfileComparer, type, path, message, IssueSeverity.FATAL));
|
||||
XhtmlNode ul;
|
||||
if ("ul".equals(x.getName())) {
|
||||
ul = x;
|
||||
} else {
|
||||
ul = null;
|
||||
for (XhtmlNode c : x.getChildNodes()) {
|
||||
if ("ul".equals(c.getName())) {
|
||||
ul = c;
|
||||
}
|
||||
}
|
||||
if (ul == null) {
|
||||
ul = x.ul();
|
||||
}
|
||||
}
|
||||
ul.li().b().style("color: maroon").addText(message);
|
||||
}
|
||||
|
||||
private void error(XhtmlNode x, IssueType type, String path, String message) {
|
||||
output.add(new ValidationMessage(Source.ProfileComparer, type, path, message, IssueSeverity.ERROR));
|
||||
XhtmlNode ul;
|
||||
if ("ul".equals(x.getName())) {
|
||||
ul = x;
|
||||
} else {
|
||||
ul = null;
|
||||
for (XhtmlNode c : x.getChildNodes()) {
|
||||
if ("ul".equals(c.getName())) {
|
||||
ul = c;
|
||||
}
|
||||
}
|
||||
if (ul == null) {
|
||||
ul = x.ul();
|
||||
}
|
||||
}
|
||||
ul.li().b().style("color: maroon").addText(message);
|
||||
}
|
||||
|
||||
private void information(XhtmlNode x, IssueType type, String path, String message) {
|
||||
if (type != null)
|
||||
output.add(new ValidationMessage(Source.ProfileComparer, type, path, message, IssueSeverity.INFORMATION));
|
||||
XhtmlNode ul;
|
||||
if ("ul".equals(x.getName())) {
|
||||
ul = x;
|
||||
} else {
|
||||
ul = null;
|
||||
for (XhtmlNode c : x.getChildNodes()) {
|
||||
if ("ul".equals(c.getName())) {
|
||||
ul = c;
|
||||
}
|
||||
}
|
||||
if (ul == null) {
|
||||
ul = x.ul();
|
||||
}
|
||||
}
|
||||
ul.li().addText(message);
|
||||
}
|
||||
|
||||
private XhtmlNode same(String text, boolean test) {
|
||||
XhtmlNode span = new XhtmlNode(NodeType.Element, "span");
|
||||
if (test)
|
||||
span.style("background-color: #bbff99; border: 1px solid #44cc00; margin-width: 10px");
|
||||
span.tx(text);
|
||||
return span;
|
||||
}
|
||||
|
||||
}
|
|
@ -33,7 +33,10 @@ 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.conformance.ProfileComparer.ProfileComparison;
|
||||
import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.formats.IParser;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
|
@ -58,12 +61,16 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
|
|||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.r5.utils.DefinitionNavigator;
|
||||
import org.hl7.fhir.r5.utils.NarrativeGenerator;
|
||||
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Logger.LogMessageType;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
|
||||
|
||||
/**
|
||||
* A engine that generates difference analysis between two sets of structure
|
||||
|
@ -83,7 +90,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
|||
* @author Grahame Grieve
|
||||
*
|
||||
*/
|
||||
public class ProfileComparer {
|
||||
public class ProfileComparer implements ProfileKnowledgeProvider {
|
||||
|
||||
private IWorkerContext context;
|
||||
|
||||
|
@ -223,28 +230,28 @@ public class ProfileComparer {
|
|||
return jp.composeString(val, "value");
|
||||
}
|
||||
|
||||
public String getErrorCount() {
|
||||
public int getErrorCount() {
|
||||
int c = 0;
|
||||
for (ValidationMessage vm : messages)
|
||||
if (vm.getLevel() == ValidationMessage.IssueSeverity.ERROR)
|
||||
c++;
|
||||
return Integer.toString(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
public String getWarningCount() {
|
||||
public int getWarningCount() {
|
||||
int c = 0;
|
||||
for (ValidationMessage vm : messages)
|
||||
if (vm.getLevel() == ValidationMessage.IssueSeverity.WARNING)
|
||||
c++;
|
||||
return Integer.toString(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
public String getHintCount() {
|
||||
public int getHintCount() {
|
||||
int c = 0;
|
||||
for (ValidationMessage vm : messages)
|
||||
if (vm.getLevel() == ValidationMessage.IssueSeverity.INFORMATION)
|
||||
c++;
|
||||
return Integer.toString(c);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -644,7 +651,7 @@ public class ProfileComparer {
|
|||
re = context.expandVS(rvs, true, false);
|
||||
if (!closed(le.getValueset()) || !closed(re.getValueset()))
|
||||
throw new DefinitionException("unclosed value sets are not handled yet");
|
||||
cvs = intersectByExpansion(lvs, rvs);
|
||||
cvs = intersectByExpansion(path, le.getValueset(), re.getValueset());
|
||||
if (!cvs.getCompose().hasInclude()) {
|
||||
outcome.messages.add(new ValidationMessage(Source.ProfileComparer, ValidationMessage.IssueType.STRUCTURE, path, "The value sets "+lvs.getUrl()+" and "+rvs.getUrl()+" do not intersect", ValidationMessage.IssueSeverity.ERROR));
|
||||
status(subset, ProfileUtilities.STATUS_ERROR);
|
||||
|
@ -688,6 +695,7 @@ public class ProfileComparer {
|
|||
|
||||
private ValueSet unite(ElementDefinition ed, ProfileComparison outcome, String path, ValueSet lvs, ValueSet rvs) {
|
||||
ValueSet vs = new ValueSet();
|
||||
vs.setName(path);
|
||||
if (lvs.hasCompose()) {
|
||||
for (ConceptSetComponent inc : lvs.getCompose().getInclude())
|
||||
vs.getCompose().getInclude().add(inc);
|
||||
|
@ -712,7 +720,7 @@ public class ProfileComparer {
|
|||
for (ConceptSetComponent dst : include) {
|
||||
if (Base.compareDeep(dst, inc, false))
|
||||
return true; // they're actually the same
|
||||
if (dst.getSystem().equals(inc.getSystem())) {
|
||||
if (dst.hasSystem() && dst.getSystem().equals(inc.getSystem())) {
|
||||
if (inc.hasFilter() || dst.hasFilter()) {
|
||||
return false; // just add the new one as a a parallel
|
||||
} else if (inc.hasConcept() && dst.hasConcept()) {
|
||||
|
@ -749,9 +757,10 @@ public class ProfileComparer {
|
|||
return null;
|
||||
}
|
||||
|
||||
private ValueSet intersectByExpansion(ValueSet lvs, ValueSet rvs) {
|
||||
private ValueSet intersectByExpansion(String path, ValueSet lvs, ValueSet rvs) {
|
||||
// this is pretty straight forward - we intersect the lists, and build a compose out of the intersection
|
||||
ValueSet vs = new ValueSet();
|
||||
vs.setName(path);
|
||||
vs.setStatus(PublicationStatus.DRAFT);
|
||||
|
||||
Map<String, ValueSetExpansionContainsComponent> left = new HashMap<String, ValueSetExpansionContainsComponent>();
|
||||
|
@ -840,8 +849,8 @@ public class ProfileComparer {
|
|||
tfound = true;
|
||||
c.setTargetProfile(r.getTargetProfile());
|
||||
} else {
|
||||
StructureDefinition sdl = resolveProfile(ed, outcome, path, l.getProfile().get(0).getValue(), outcome.leftName());
|
||||
StructureDefinition sdr = resolveProfile(ed, outcome, path, r.getProfile().get(0).getValue(), outcome.rightName());
|
||||
StructureDefinition sdl = resolveProfile(ed, outcome, path, l.getTargetProfile().get(0).getValue(), outcome.leftName());
|
||||
StructureDefinition sdr = resolveProfile(ed, outcome, path, r.getTargetProfile().get(0).getValue(), outcome.rightName());
|
||||
if (sdl != null && sdr != null) {
|
||||
if (sdl == sdr) {
|
||||
tfound = true;
|
||||
|
@ -1187,7 +1196,41 @@ public class ProfileComparer {
|
|||
}
|
||||
|
||||
private String genPCLink(String leftName, String leftLink) {
|
||||
return "<a href=\""+leftLink+"\">"+Utilities.escapeXml(leftName)+"</a>";
|
||||
if (leftLink == null)
|
||||
return leftName;
|
||||
else
|
||||
return "<a href=\""+leftLink+"\">"+Utilities.escapeXml(leftName)+"</a>";
|
||||
}
|
||||
|
||||
private String genValueSets(String base) throws IOException {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("<ul>\r\n");
|
||||
for (ValueSet vs : getValuesets()) {
|
||||
b.append("<li>");
|
||||
b.append(" <td><a href=\""+base+"-"+vs.getId()+".html\">"+Utilities.escapeXml(vs.present())+"</a></td>");
|
||||
b.append("</li>\r\n");
|
||||
genValueSetFile(base+"-"+vs.getId()+".html", vs);
|
||||
}
|
||||
b.append("</ul>\r\n");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private void genValueSetFile(String filename, ValueSet vs) throws IOException {
|
||||
NarrativeGenerator gen = new NarrativeGenerator("", "http://hl7.org/fhir", context);
|
||||
gen.generate(null, vs, false);
|
||||
String s = new XhtmlComposer(XhtmlComposer.HTML).compose(vs.getText().getDiv());
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("<html>");
|
||||
b.append("<head>");
|
||||
b.append("<title>"+vs.present()+"</title>");
|
||||
b.append("<link rel=\"stylesheet\" href=\"fhir.css\"/>\r\n");
|
||||
b.append("</head>");
|
||||
b.append("<body>");
|
||||
b.append("<h2>"+vs.present()+"</h2>");
|
||||
b.append(s);
|
||||
b.append("</body>");
|
||||
b.append("</html>");
|
||||
TextFile.stringToFile(b.toString(), filename);
|
||||
}
|
||||
|
||||
private String genPCTable() {
|
||||
|
@ -1219,7 +1262,53 @@ public class ProfileComparer {
|
|||
}
|
||||
|
||||
|
||||
private String genCmpMessages(ProfileComparison cmp) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("<table class=\"grid\">\r\n");
|
||||
b.append("<tr><td><b>Path</b></td><td><b>Message</b></td></tr>\r\n");
|
||||
b.append("<tr><td colspan=\"2\" style=\"background: #eeeeee\">Errors Detected</td></tr>\r\n");
|
||||
boolean found = false;
|
||||
for (ValidationMessage vm : cmp.getMessages())
|
||||
if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
|
||||
found = true;
|
||||
b.append("<tr><td>"+vm.getLocation()+"</td><td>"+vm.getHtml()+(vm.getLevel() == IssueSeverity.FATAL ? "(<span style=\"color: maroon\">This error terminated the comparison process</span>)" : "")+"</td></tr>\r\n");
|
||||
}
|
||||
if (!found)
|
||||
b.append("<tr><td colspan=\"2\">(None)</td></tr>\r\n");
|
||||
|
||||
boolean first = true;
|
||||
for (ValidationMessage vm : cmp.getMessages())
|
||||
if (vm.getLevel() == IssueSeverity.WARNING) {
|
||||
if (first) {
|
||||
first = false;
|
||||
b.append("<tr><td colspan=\"2\" style=\"background: #eeeeee\">Warnings about the comparison</td></tr>\r\n");
|
||||
}
|
||||
b.append("<tr><td>"+vm.getLocation()+"</td><td>"+vm.getHtml()+"</td></tr>\r\n");
|
||||
}
|
||||
first = true;
|
||||
for (ValidationMessage vm : cmp.getMessages())
|
||||
if (vm.getLevel() == IssueSeverity.INFORMATION) {
|
||||
if (first) {
|
||||
b.append("<tr><td colspan=\"2\" style=\"background: #eeeeee\">Notes about differences (e.g. definitions)</td></tr>\r\n");
|
||||
first = false;
|
||||
}
|
||||
b.append("<tr><td>"+vm.getLocation()+"</td><td>"+vm.getHtml()+"</td></tr>\r\n");
|
||||
}
|
||||
b.append("</table>\r\n");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private String genCompModel(StructureDefinition sd, String name, String base, String prefix, String dest) throws FHIRException, IOException {
|
||||
if (sd == null)
|
||||
return "<p style=\"color: maroon\">No "+name+" could be generated</p>\r\n";
|
||||
return new XhtmlComposer(XhtmlComposer.HTML).compose(new ProfileUtilities(context, null, this).generateTable("??", sd, false, dest, false, base, true, prefix, prefix, false, false, null));
|
||||
}
|
||||
|
||||
|
||||
public String generate(String dest) throws IOException {
|
||||
for (ValueSet vs : valuesets) {
|
||||
vs.setUserData("path", dest+"/"+getId()+"-vs-"+vs.getId()+".html");
|
||||
}
|
||||
// ok, all compared; now produce the output
|
||||
// first page we produce is simply the index
|
||||
Map<String, String> vars = new HashMap<String, String>();
|
||||
|
@ -1227,21 +1316,20 @@ public class ProfileComparer {
|
|||
vars.put("left", genPCLink(getLeftName(), getLeftLink()));
|
||||
vars.put("right", genPCLink(getRightName(), getRightLink()));
|
||||
vars.put("table", genPCTable());
|
||||
vars.put("valuesets", genValueSets(dest+"/"+getId()+"-vs"));
|
||||
producePage(summaryTemplate(), Utilities.path(dest, getId()+".html"), vars);
|
||||
|
||||
// page.log(" ... generate", LogMessageType.Process);
|
||||
// String src = TextFile.fileToString(page.getFolders().srcDir + "template-comparison-set.html");
|
||||
// src = page.processPageIncludes(n+".html", src, "?type", null, "??path", null, null, "Comparison", pc, null, null, page.getDefinitions().getWorkgroups().get("fhir"));
|
||||
// TextFile.stringToFile(src, Utilities.path(page.getFolders().dstDir, n+".html"));
|
||||
// cachePage(n + ".html", src, "Comparison "+pc.getTitle(), false);
|
||||
//
|
||||
// // then we produce a comparison page for each pair
|
||||
// for (ProfileComparison cmp : pc.getComparisons()) {
|
||||
// src = TextFile.fileToString(page.getFolders().srcDir + "template-comparison.html");
|
||||
// src = page.processPageIncludes(n+"."+cmp.getId()+".html", src, "?type", null, "??path", null, null, "Comparison", cmp, null, null, page.getDefinitions().getWorkgroups().get("fhir"));
|
||||
// TextFile.stringToFile(src, Utilities.path(page.getFolders().dstDir, n+"."+cmp.getId()+".html"));
|
||||
// cachePage(n +"."+cmp.getId()+".html", src, "Comparison "+pc.getTitle(), false);
|
||||
// }
|
||||
// then we produce a comparison page for each pair
|
||||
for (ProfileComparison cmp : getComparisons()) {
|
||||
vars.clear();
|
||||
vars.put("title", getTitle());
|
||||
vars.put("left", genPCLink(getLeftName(), getLeftLink()));
|
||||
vars.put("right", genPCLink(getRightName(), getRightLink()));
|
||||
vars.put("messages", genCmpMessages(cmp));
|
||||
vars.put("subset", genCompModel(cmp.getSubset(), "intersection", getId()+"."+cmp.getId(), "", dest));
|
||||
vars.put("superset", genCompModel(cmp.getSuperset(), "union", getId()+"."+cmp.getId(), "", dest));
|
||||
producePage(singleTemplate(), Utilities.path(dest, getId()+"."+cmp.getId()+".html"), vars);
|
||||
}
|
||||
// // and also individual pages for each pair outcome
|
||||
// // then we produce value set pages for each value set
|
||||
//
|
||||
|
@ -1249,6 +1337,7 @@ public class ProfileComparer {
|
|||
return Utilities.path(dest, getId()+".html");
|
||||
}
|
||||
|
||||
|
||||
private void producePage(String src, String path, Map<String, String> vars) throws IOException {
|
||||
while (src.contains("[%"))
|
||||
{
|
||||
|
@ -1264,7 +1353,13 @@ public class ProfileComparer {
|
|||
}
|
||||
|
||||
private String summaryTemplate() throws IOException {
|
||||
return cachedFetch("04a9d69a-47f2-4250-8645-bf5d880a8eaa-1.fhir-template", "http://build.fhir.org/template-comparison-set.html.template");
|
||||
return TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\source\\template-comparison-set.html");
|
||||
// return cachedFetch("04a9d69a-47f2-4250-8645-bf5d880a8eaa-1.fhir-template", "http://build.fhir.org/template-comparison-set.html.template");
|
||||
}
|
||||
|
||||
private String singleTemplate() throws IOException {
|
||||
return TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\source\\template-comparison.html");
|
||||
// return cachedFetch("04a9d69a-47f2-4250-8645-bf5d880a8eaa-1.fhir-template", "http://build.fhir.org/template-comparison-set.html.template");
|
||||
}
|
||||
|
||||
private String cachedFetch(String id, String source) throws IOException {
|
||||
|
@ -1280,6 +1375,85 @@ public class ProfileComparer {
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDatatype(String typeSimple) {
|
||||
throw new Error("Not done yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResource(String typeSimple) {
|
||||
throw new Error("Not done yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLinkFor(String name) {
|
||||
StructureDefinition sd = context.fetchTypeDefinition(name);
|
||||
return sd != null && sd.hasUserData("path");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLinkFor(String corePath, String name) {
|
||||
StructureDefinition sd = context.fetchTypeDefinition(name);
|
||||
return sd == null ? null : sd.getUserString("path");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) throws FHIRException {
|
||||
return resolveBindingInt(def, binding.getValueSet(), binding.getDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException {
|
||||
return resolveBindingInt(def, url, url);
|
||||
}
|
||||
|
||||
public BindingResolution resolveBindingInt(StructureDefinition def, String url, String desc) throws FHIRException {
|
||||
ValueSet vs = null;
|
||||
if (url.startsWith("#")) {
|
||||
for (ValueSet t : valuesets) {
|
||||
if (("#"+t.getId()).equals(url)) {
|
||||
vs = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vs == null)
|
||||
context.fetchResource(ValueSet.class, url);
|
||||
BindingResolution br = new BindingResolution();
|
||||
if (vs != null) {
|
||||
br.display = vs.present();
|
||||
br.url = vs.getUserString("path");
|
||||
} else {
|
||||
br.display = desc;
|
||||
}
|
||||
return br;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLinkForProfile(StructureDefinition profile, String url) {
|
||||
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
|
||||
return sd == null ? null : sd.getUserString("path");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prependLinks() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLinkForUrl(String corePath, String s) {
|
||||
throw new Error("Not done yet");
|
||||
}
|
||||
|
||||
public int getErrCount() {
|
||||
int res = 0;
|
||||
for (ProfileComparison pc : comparisons) {
|
||||
res = res + pc.getErrorCount();
|
||||
}
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2337,7 +2337,7 @@ public class ProfileUtilities extends TranslatingUtilities {
|
|||
if (sd != null) {
|
||||
String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
|
||||
String ref = pkp.getLinkForProfile(null, sd.getUrl());
|
||||
if (ref.contains("|"))
|
||||
if (ref != null && ref.contains("|"))
|
||||
ref = ref.substring(0, ref.indexOf("|"));
|
||||
c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
|
||||
} else
|
||||
|
|
|
@ -558,8 +558,12 @@ public class XhtmlNode implements IBaseXhtml {
|
|||
}
|
||||
|
||||
|
||||
public void code(String text) {
|
||||
addTag("code").tx(text);
|
||||
public XhtmlNode code(String text) {
|
||||
return addTag("code").tx(text);
|
||||
}
|
||||
|
||||
public XhtmlNode code() {
|
||||
return addTag("code");
|
||||
}
|
||||
|
||||
|
||||
|
@ -613,4 +617,28 @@ public class XhtmlNode implements IBaseXhtml {
|
|||
return notPretty;
|
||||
}
|
||||
|
||||
|
||||
public XhtmlNode style(String style) {
|
||||
setAttribute("style", style);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public XhtmlNode nbsp() {
|
||||
return addText(NBSP);
|
||||
}
|
||||
|
||||
|
||||
public XhtmlNode para(String text) {
|
||||
XhtmlNode p = para();
|
||||
p.addText(text);
|
||||
return p;
|
||||
|
||||
}
|
||||
|
||||
public XhtmlNode add(XhtmlNode n) {
|
||||
getChildNodes().add(n);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -534,7 +534,7 @@ public class ValidationEngine {
|
|||
public Map<String, byte[]> loadPackage(NpmPackage pi) throws IOException {
|
||||
Map<String, byte[]> res = new HashMap<String, byte[]>();
|
||||
for (String s : pi.list("package")) {
|
||||
if (s.startsWith("CodeSystem-") || s.startsWith("ConceptMap-") || s.startsWith("ImplementationGuide-") || s.startsWith("StructureMap-") || s.startsWith("ValueSet-") || s.startsWith("StructureDefinition-"))
|
||||
if (s.startsWith("CodeSystem-") || s.startsWith("ConceptMap-") || s.startsWith("ImplementationGuide-") || s.startsWith("CapabilityStatement-") || s.startsWith("StructureMap-") || s.startsWith("ValueSet-") || s.startsWith("StructureDefinition-"))
|
||||
res.put(s, TextFile.streamToBytes(pi.load("package", s)));
|
||||
}
|
||||
String ini = "[FHIR]\r\nversion="+pi.fhirVersion()+"\r\n";
|
||||
|
|
|
@ -61,6 +61,8 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementKind;
|
||||
import org.hl7.fhir.r5.conformance.CapabilityStatementUtilities;
|
||||
import org.hl7.fhir.r5.conformance.ProfileComparer;
|
||||
import org.hl7.fhir.r5.formats.IParser;
|
||||
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||
|
@ -68,18 +70,23 @@ import org.hl7.fhir.r5.formats.XmlParser;
|
|||
import org.hl7.fhir.r5.formats.JsonParser;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
|
||||
import org.hl7.fhir.r5.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r5.model.Constants;
|
||||
import org.hl7.fhir.r5.model.DomainResource;
|
||||
import org.hl7.fhir.r5.model.FhirPublication;
|
||||
import org.hl7.fhir.r5.model.ImplementationGuide;
|
||||
import org.hl7.fhir.r5.model.IntegerType;
|
||||
import org.hl7.fhir.r5.model.MetadataResource;
|
||||
import org.hl7.fhir.r5.model.OperationOutcome;
|
||||
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtil;
|
||||
import org.hl7.fhir.utilities.cache.ToolsVersion;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
|
||||
|
||||
/**
|
||||
|
@ -218,6 +225,7 @@ public class Validator {
|
|||
else if ("1.0".equals(v)) v = "1.0.2";
|
||||
else if ("1.4".equals(v)) v = "1.4.0";
|
||||
else if ("3.0".equals(v)) v = "3.0.1";
|
||||
else if ("4.0".equals(v)) v = "4.0.0";
|
||||
else if (v.startsWith(Constants.VERSION)) v = Constants.VERSION;
|
||||
String definitions = "hl7.fhir.core#"+v;
|
||||
System.out.println("Loading (v = "+v+", tx server http://tx.fhir.org)");
|
||||
|
@ -236,24 +244,53 @@ public class Validator {
|
|||
}
|
||||
}
|
||||
// ok now set up the comparison
|
||||
ProfileComparer pc = new ProfileComparer(validator.getContext());
|
||||
String left = getParam(args, "-left");
|
||||
String right = getParam(args, "-right");
|
||||
StructureDefinition sdL = validator.getContext().fetchResource(StructureDefinition.class, left);
|
||||
if (sdL == null) {
|
||||
System.out.println("Unable to locate left profile " +left);
|
||||
} else {
|
||||
StructureDefinition sdR = validator.getContext().fetchResource(StructureDefinition.class, right);
|
||||
if (sdR == null) {
|
||||
System.out.println("Unable to locate right profile " +right);
|
||||
} else {
|
||||
System.out.println("Comparing "+left+" to "+right);
|
||||
Resource resLeft = validator.getContext().fetchResource(Resource.class, left);
|
||||
Resource resRight = validator.getContext().fetchResource(Resource.class, right);
|
||||
if (resLeft == null) {
|
||||
System.out.println("Unable to locate left resource " +left);
|
||||
}
|
||||
if (resRight == null) {
|
||||
System.out.println("Unable to locate right resource " +right);
|
||||
}
|
||||
if (resLeft != null && resRight != null) {
|
||||
if (resLeft instanceof StructureDefinition && resRight instanceof StructureDefinition) {
|
||||
System.out.println("Comparing StructureDefinitions "+left+" to "+right);
|
||||
ProfileComparer pc = new ProfileComparer(validator.getContext());
|
||||
StructureDefinition sdL = (StructureDefinition) resLeft;
|
||||
StructureDefinition sdR = (StructureDefinition) resRight;
|
||||
pc.compareProfiles(sdL, sdR);
|
||||
System.out.println("Generating output...");
|
||||
System.out.println("Generating output to "+dest+"...");
|
||||
File htmlFile = new File(pc.generate(dest));
|
||||
Desktop.getDesktop().browse(htmlFile.toURI());
|
||||
System.out.println("Done");
|
||||
}
|
||||
} else if (resLeft instanceof CapabilityStatement && resRight instanceof CapabilityStatement) {
|
||||
String nameLeft = chooseName(args, "leftName", (MetadataResource) resLeft);
|
||||
String nameRight = chooseName(args, "rightName", (MetadataResource) resRight);
|
||||
System.out.println("Comparing CapabilityStatements "+left+" to "+right);
|
||||
CapabilityStatementUtilities pc = new CapabilityStatementUtilities(validator.getContext(), dest);
|
||||
CapabilityStatement capL = (CapabilityStatement) resLeft;
|
||||
CapabilityStatement capR = (CapabilityStatement) resRight;
|
||||
List<ValidationMessage> msgs = pc.isCompatible(nameLeft, nameRight, capL, capR);
|
||||
|
||||
String destTxt = Utilities.path(dest, "output.txt");
|
||||
System.out.println("Generating output to "+destTxt+"...");
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (ValidationMessage msg : msgs) {
|
||||
b.append(msg.summary());
|
||||
b.append("\r\n");
|
||||
}
|
||||
TextFile.stringToFile(b.toString(), destTxt);
|
||||
File txtFile = new File(destTxt);
|
||||
Desktop.getDesktop().browse(txtFile.toURI());
|
||||
|
||||
String destHtml = Utilities.path(dest, "index.html");
|
||||
File htmlFile = new File(destHtml);
|
||||
Desktop.getDesktop().browse(htmlFile.toURI());
|
||||
System.out.println("Done");
|
||||
} else
|
||||
System.out.println("Unable to compare left resource " +left+" ("+resLeft.fhirType()+") with right resource "+right+" ("+resRight.fhirType()+")");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -476,6 +513,13 @@ public class Validator {
|
|||
}
|
||||
}
|
||||
|
||||
private static String chooseName(String[] args, String name, MetadataResource mr) {
|
||||
String s = getParam(args, "-"+name);
|
||||
if (Utilities.noString(s))
|
||||
s = mr.present();
|
||||
return s;
|
||||
}
|
||||
|
||||
private static String getGitBuild() {
|
||||
return "??";
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue