diff --git a/org.hl7.fhir.publisher.cli/pom.xml b/org.hl7.fhir.publisher.cli/pom.xml index 5266c2a85..b84aafce5 100644 --- a/org.hl7.fhir.publisher.cli/pom.xml +++ b/org.hl7.fhir.publisher.cli/pom.xml @@ -5,7 +5,7 @@ org.hl7.fhir.publisher org.hl7.fhir.publisher - 1.1.117-SNAPSHOT + 1.1.121-SNAPSHOT ../pom.xml 4.0.0 diff --git a/org.hl7.fhir.publisher.core/pom.xml b/org.hl7.fhir.publisher.core/pom.xml index ce2c7dc4e..e771ff20d 100644 --- a/org.hl7.fhir.publisher.core/pom.xml +++ b/org.hl7.fhir.publisher.core/pom.xml @@ -5,7 +5,7 @@ org.hl7.fhir.publisher org.hl7.fhir.publisher - 1.1.117-SNAPSHOT + 1.1.121-SNAPSHOT ../pom.xml 4.0.0 diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/DependentIGFinder.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/DependentIGFinder.java new file mode 100644 index 000000000..d1637e5e2 --- /dev/null +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/DependentIGFinder.java @@ -0,0 +1,591 @@ +package org.hl7.fhir.igtools.publisher; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.igtools.publisher.DependentIGFinder.DepInfo; +import org.hl7.fhir.igtools.publisher.DependentIGFinder.DeplistSorter; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.json.JSONUtil; +import org.hl7.fhir.utilities.json.JsonTrackingParser; +import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; +import org.hl7.fhir.utilities.npm.NpmPackage; +import org.hl7.fhir.utilities.npm.PackageClient; +import org.hl7.fhir.utilities.npm.ToolsVersion; +import org.stringtemplate.v4.ST; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class DependentIGFinder { + + public class Triple { + private String url; + private String text; + private String link; + public Triple(String url, String text, String link) { + super(); + this.url = url; + this.text = text; + this.link = link; + } + + } + + public class DeplistSorter implements Comparator { + + @Override + public int compare(DepInfo arg0, DepInfo arg1) { + return arg0.pid.compareTo(arg1.pid); + } + } + + public class DepInfoDetails { + public DepInfoDetails(String version, String path) { + this.version = version; + this.path = path; + } + private String version; + private String path; + private Map> codesystemsVs = new HashMap<>(); + private Map> codesystemsExamples = new HashMap<>(); + private Map> valuesetsInc = new HashMap<>(); + private Map> valuesetsBind = new HashMap<>(); + private Map> profilesRef = new HashMap<>(); + private Map> profilesDeriv = new HashMap<>(); + private Map> extensionsRef = new HashMap<>(); + private Map> extensionsDeriv = new HashMap<>(); + private Map> operations = new HashMap<>(); + private Map> searchParams = new HashMap<>(); + private Map> capabilitiesInst = new HashMap<>(); + private Map> capabilitiesImpl = new HashMap<>(); + + } + + public class DepInfo { + private String path; + private String pid; + private DepInfoDetails cibuild; + private DepInfoDetails ballot; + private DepInfoDetails published; + public DepInfo(String pid, String path) { + super(); + this.pid = pid; + this.path = path; + } + } + + private List deplist = new ArrayList<>(); + private List errors = new ArrayList<>(); + + private String id; // the id of the IG in question + private String outcome; // html rendering + private boolean debug = false; + private boolean working = true; + private FilesystemPackageCacheManager pcm; + private Map triples = new HashMap<>(); + private List codeSystems = new ArrayList<>(); + private List valueSets = new ArrayList<>(); + private List profiles = new ArrayList<>(); + private List extensions = new ArrayList<>(); + private List logicals = new ArrayList<>(); + private List searchParams = new ArrayList<>(); + private List capabilityStatements = new ArrayList<>(); + private List examples = new ArrayList<>(); + + private String countDesc; + private String summary; + private String details1; + private String details2; + + public DependentIGFinder(String id) throws IOException { + super(); + this.id = id; + pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); + pcm.setSilent(true); + outcome = "Finding Dependent IGs not done yet"; + } + + public void go() { + startThread(); +// analyse(); + } + + private void startThread() { + new Thread(new Runnable() { + @Override + public void run() { + try { + analyse(); + } catch (Exception e) { + if (debug) System.out.println("Processing DependentIGs failed: "+e.getMessage()); + } + } + + }).start(); + } + + private void analyse() { + try { + Set plist = getDependentPackages(); + JsonObject json = JsonTrackingParser.fetchJson("https://raw.githubusercontent.com/FHIR/ig-registry/master/fhir-ig-list.json"); + + for (String pid : plist) { + JsonObject guide = getGuide(json, pid); + if (guide != null) { + checkIGDependencies(guide); + } + } + render(); + working = false; + } catch (Exception e) { + errors.add("Unable to process: " +e.getMessage()); + outcome = "Error analysing dependencies: "+Utilities.escapeXml(e.getMessage())+""; + } + } + + private Set getDependentPackages() { + Set list = new HashSet<>(); +// getDependentPackages(list, PackageClient.PRIMARY_SERVER); + getDependentPackages(list, PackageClient.SECONDARY_SERVER); + return list; + } + + private void getDependentPackages(Set list, String url) { + PackageClient client = new PackageClient(url); + try { + client.findDependents(list, id); + } catch (Exception e) { + // nothing + } + } + + private JsonObject getGuide(JsonObject json, String pid) { + for (JsonObject o : JSONUtil.objects(json, "guides")) { + if (pid.equals(JSONUtil.str(o, "npm-name"))) { + return o; + } + } + return null; + } + + private void render() { + Collections.sort(deplist, new DeplistSorter()); + StringBuilder bs = new StringBuilder(); + StringBuilder bd1 = new StringBuilder(); + StringBuilder bd2 = new StringBuilder(); + int c = 0; + bs.append(""); + bs.append(""); + bs.append(""); + bs.append(""); + bs.append(""); + bs.append(""); + bs.append(""); + for (DepInfo dep : deplist) { + if (isFound(dep.ballot) || isFound(dep.cibuild) || isFound(dep.published)) { + c++; + bs.append(""); + bs.append(""); + bs.append(""); + bs.append(""); + bs.append(""); + bs.append(""); + if (isFound(dep.cibuild)) { + details(bd1, dep, dep.cibuild); + } else if (isFound(dep.published)) { + details(bd1, dep, dep.published); + } else if (isFound(dep.ballot)) { + details(bd1, dep, dep.ballot); + } + } + } + bs.append("
idpublishedballotcurrent
"+dep.pid+""+present(dep.pid, dep.published, true)+""+present(dep.pid, dep.ballot, true)+""+present(dep.pid, dep.cibuild, false)+"
"); + + generate(bd2, codeSystems, "CodeSystems"); + generate(bd2, valueSets, "ValueSets"); + generate(bd2, profiles, "Profiles"); + generate(bd2, extensions, "Extensions"); + + if (c == 0) { + countDesc = "no references"; + summary = "

no references

"; + details1 = "

(no details)

"; + details2 = "

(no details)

"; + } else { + countDesc = ""+c+" "+Utilities.pluralize("guide", c); + summary = bs.toString(); + details1 = bd1.toString(); + details2 = bd2.toString(); + } + } + + private void generate(StringBuilder bd2, List list, String title) { + bd2.append("

"+title+"

\r\n"); + + bd2.append(""); + for (String s : sorted(list)) { + Triple t = triples.get(s); + bd2.append(""); + bd2.append(" \r\n"); + bd2.append(" \r\n"); + bd2.append(" \r\n"); + } + bd2.append("
"+Utilities.escapeXml(t.text)+"\r\n"); + int c = 0; + for (DepInfo dep : deplist) { + if (isFound(dep.cibuild)) { + c = processDep(bd2, s, dep, dep.cibuild, c); + } else if (isFound(dep.ballot)) { + c = processDep(bd2, s, dep, dep.ballot, c); + } else if (isFound(dep.published)) { + c = processDep(bd2, s, dep, dep.published, c); + } + } + if (c == 0) { + bd2.append("(not used)"); + } + bd2.append("
"); + } + + private int processDep(StringBuilder bd2, String url, DepInfo dep, DepInfoDetails di, int c) { + c = processDepList(bd2, url, dep, di.codesystemsVs, dep.pid, c, "Used In"); + c = processDepList(bd2, url, dep, di.codesystemsExamples, dep.pid, c, "Used In"); + c = processDepList(bd2, url, dep, di.valuesetsBind, dep.pid, c, "Bound In"); + c = processDepList(bd2, url, dep, di.valuesetsInc, dep.pid, c, "Imported In"); + c = processDepList(bd2, url, dep, di.profilesDeriv, dep.pid, c, "Constrained In"); + c = processDepList(bd2, url, dep, di.profilesRef, dep.pid, c, "Used In"); + c = processDepList(bd2, url, dep, di.extensionsDeriv, dep.pid, c, "Constrained In"); + c = processDepList(bd2, url, dep, di.extensionsRef, dep.pid, c, "Used In"); + return c; + } + + private int processDepList(StringBuilder bd2, String url, DepInfo dep, Map> map, String pid, int c, String label) { + List list = map.get(url); + if (list != null) { + for (Triple t : list) { + if (c > 0) { + bd2.append("
"); + } + c++; + bd2.append(""+label+" "+Utilities.escapeXml(t.text)+" ("+pid+")\r\n"); + } + } + return c; + } + + private List sorted(List list) { + List res = new ArrayList<>(); + res.addAll(list); + Collections.sort(res); + return res; + } + + private void details(StringBuilder bd1, DepInfo dep, DepInfoDetails di) { + bd1.append("

"+dep.pid+"#"+di.version+"

\r\n"); + generateDetails(bd1, "CodeSystems used in ValueSets", di.codesystemsVs); + generateDetails(bd1, "ValueSets used in profiles", di.valuesetsBind); + generateDetails(bd1, "Derived profiles", di.profilesDeriv); + generateDetails(bd1, "Used profiles", di.profilesRef); + generateDetails(bd1, "Derived extensions", di.extensionsDeriv); + generateDetails(bd1, "Used Extensions", di.extensionsRef); + } + + private void generateDetails(StringBuilder bd1, String head, Map> list) { + if (!list.isEmpty()) { + bd1.append("

"+head+"

\r\n\r\n"); + for (String n : list.keySet()) { + bd1.append(" \r\n"); + Triple t = triples.get(n); + bd1.append(" \r\n"); + bd1.append(" \r\n"); + bd1.append(" \r\n"); + } + bd1.append("
"+Utilities.escapeXml(t.text)+"\r\n"); + boolean first = true; + for (Triple p : list.get(n)) { + if (first) { first = false; } else { bd1.append("
"); } + bd1.append(" "+Utilities.escapeXml(p.text)+"\r\n"); + } + bd1.append("
"); + } + } + + private String present(String id, DepInfoDetails s, boolean currentWrong) { + if (s == null) { + return ""; + } + if (s.version.equals("??")) { + return "??"; + } + if (s.version.equals("current") && currentWrong) { + return "current"; + } + return ""+s.version+""; + } + + private boolean isFound(DepInfoDetails s) { + return s != null && !s.version.equals("??"); + } + + private void checkIGDependencies(JsonObject guide) { + String pid = JSONUtil.str(guide, "npm-name"); +// System.out.println("check "+pid+" " +abc); + + // we only check the latest published version, and the CI build + try { + JsonObject pl = JsonTrackingParser.fetchJson(Utilities.pathURL(guide.get("canonical").getAsString(), "package-list.json")); + String canonical = JSONUtil.str(guide, "canonical"); + DepInfo dep = new DepInfo(pid, Utilities.path(canonical, "history.html")); + deplist.add(dep); + for (JsonObject list : JSONUtil.objects(pl, "list")) { + boolean ballot = false; + String version = JSONUtil.str(list, "version"); + if (!"current".equals(version)) { + String status = JSONUtil.str(list, "status"); + if ("ballot".equals(status)) { + if (!ballot) { + ballot = true; + dep.ballot = checkForDependency(pid, version, JSONUtil.str(list, "path")); + } + } else { + dep.published = checkForDependency(pid, version, JSONUtil.str(list, "path")); + break; + } + } + } + dep.cibuild = checkForDependency(pid, "current", JSONUtil.str(guide, "ci-build")); + } catch (Exception e) { + errors.add("Unable to process "+JSONUtil.str(guide, "name")+": " +e.getMessage()); + if (debug) System.out.println("Dependency Analysis - Unable to process "+JSONUtil.str(guide, "name")+": " +e.getMessage()); + } + } + + private DepInfoDetails checkForDependency(String pid, String version, String path) throws IOException { + try { + NpmPackage npm = pcm.loadPackage(pid, version); + for (String dep : npm.dependencies()) { + if (dep.startsWith(id+"#")) { + return buildDetails(npm, path, dep.substring(dep.indexOf("#")+1)); + } + } + } catch (Exception e) { + if (!"current".equals(version)) { + errors.add("Unable to process "+pid+"#"+version+": " +e.getMessage()); + if (debug) System.out.println("Dependency Analysis - Unable to process "+pid+": " +e.getMessage()); + } + return new DepInfoDetails("??", null); + } + return null; + } + + private DepInfoDetails buildDetails(NpmPackage npm, String path, String version) throws FHIRFormatError, IOException { + DepInfoDetails res = new DepInfoDetails(version, path); + if (VersionUtilities.isR4Ver(npm.fhirVersion()) || VersionUtilities.isR4BVer(npm.fhirVersion())) { + scanR4IG(npm, res); + } else if (VersionUtilities.isR5Ver(npm.fhirVersion())) { +// scanR5IG(npm, res); + } else if (VersionUtilities.isR3Ver(npm.fhirVersion())) { +// scanR3IG(npm, res); + } + return res; + } + + private void scanR4IG(NpmPackage npm, DepInfoDetails di) throws FHIRFormatError, IOException { + try { + for (String t : npm.listResources("CodeSystem", "ValueSet", "StructureDefinition")) { + org.hl7.fhir.r4.model.Resource r = new org.hl7.fhir.r4.formats.JsonParser().parse(npm.loadResource(t)); + String link = "??"; + if (r instanceof org.hl7.fhir.r4.model.CodeSystem) { + scanCodeSystemR4((org.hl7.fhir.r4.model.CodeSystem) r, di); + } else if (r instanceof org.hl7.fhir.r4.model.ValueSet) { + scanValueSetR4((org.hl7.fhir.r4.model.ValueSet) r, di, link); + } else if (r instanceof org.hl7.fhir.r4.model.StructureDefinition) { + scanStructureDefinitionR4((org.hl7.fhir.r4.model.StructureDefinition) r, di, link); + } + } + // check the examples + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + private void scanCodeSystemR4(org.hl7.fhir.r4.model.CodeSystem cs, DepInfoDetails di) { + // todo at some stage: check properties) + + } + + private void scanValueSetR4(org.hl7.fhir.r4.model.ValueSet vs, DepInfoDetails di, String link) { + for (ConceptSetComponent t : vs.getCompose().getInclude()) { + addToMap(codeSystems, di.codesystemsVs, t.getSystem(), vs.getUrl(), vs.present(), link); + } + for (ConceptSetComponent t : vs.getCompose().getExclude()) { + addToMap(codeSystems, di.codesystemsVs, t.getSystem(), vs.getUrl(), vs.present(), link); + } + } + + private void addToMap(List set, Map> list, String url, String turl, String title, String link) { + if (set.contains(url)) { + if (!list.containsKey(url)) { + list.put(url, new ArrayList<>()); + } + List v = list.get(url); + for (Triple t : v) { + if (t.url != null && t.url.equals(turl)) { + return; + } + } + v.add(new Triple(turl, title, link)); + } + } + + private void scanStructureDefinitionR4(org.hl7.fhir.r4.model.StructureDefinition sd, DepInfoDetails di, String link) { + addToMap(profiles, di.profilesDeriv, sd.getBaseDefinition(), sd.getUrl(), sd.present(), link); + addToMap(extensions, di.extensionsDeriv, sd.getBaseDefinition(), sd.getUrl(), sd.present(), link); + + // we only scan diffs - we're not interested in repeating everything + for (org.hl7.fhir.r4.model.ElementDefinition ed : sd.getDifferential().getElement()) { + if (ed.getBinding().hasValueSet()) { + addToMap(valueSets, di.valuesetsBind, ed.getBinding().getValueSet(), sd.getUrl(), sd.present(), link); + } + for (org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent tr : ed.getType()) { + for (CanonicalType c : tr.getProfile()) { + addToMap(extensions, di.extensionsRef, c.getValue(), sd.getUrl(), sd.present(), link); + } + for (CanonicalType c : tr.getTargetProfile()) { + addToMap(profiles, di.profilesRef, c.getValue(), sd.getUrl(), sd.present(), link); + } + } + if (ed.hasFixedOrPattern()) { + if (ed.getFixedOrPattern() instanceof org.hl7.fhir.r4.model.Coding) { + org.hl7.fhir.r4.model.Coding c = (org.hl7.fhir.r4.model.Coding) ed.getFixedOrPattern(); + addToMap(codeSystems, di.codesystemsExamples, c.getSystem(), sd.getUrl(), sd.present(), link); + } + if (ed.getFixedOrPattern() instanceof org.hl7.fhir.r4.model.CodeableConcept) { + org.hl7.fhir.r4.model.CodeableConcept cc = (org.hl7.fhir.r4.model.CodeableConcept) ed.getFixedOrPattern(); + for (org.hl7.fhir.r4.model.Coding c : cc.getCoding()) { + addToMap(codeSystems, di.codesystemsExamples, c.getSystem(), sd.getUrl(), sd.present(), link); + } + } + if (ed.getFixedOrPattern() instanceof org.hl7.fhir.r4.model.Quantity) { + org.hl7.fhir.r4.model.Quantity c = (org.hl7.fhir.r4.model.Quantity) ed.getFixedOrPattern(); + addToMap(codeSystems, di.codesystemsExamples, c.getSystem(), sd.getUrl(), sd.present(), link); + } + } + } + } + + public String getId() { + return id; + } + + private ST template(String t) { + return new ST(t, '$', '$'); + } + + public void finish(String path, String title) throws IOException { + while (working) { + try { + Thread.sleep(1000); + System.out.println("Waiting for dependency analysis to complete"); + } catch (InterruptedException e) { + } + } + + String page = + "\r\n"+ + "\r\n"+ + "\r\n"+ + " $title$ : Dependent IGs Analysis\r\n"+ + " \r\n"+ + "\r\n"+ + "

Dependent IGs Analysis for $title$

\r\n"+ + "

Generated $time$ for $packageId$

\r\n"+ + "

Summary:

\r\n"+ + "$summary$\r\n"+ + "

Details By IG

\r\n"+ + "$details1$\r\n"+ + "

Details By Resource

\r\n"+ + "$details2$\r\n"+ + "

Errors

\r\n"+ + "
\r\n"+
+        "$errors$\r\n"+
+        "
\r\n"+ + "\r\n"+ + "\r\n"; + + ST t = template(page); + t.add("title", title); + t.add("time", new Date().toString()); + t.add("packageId", id); + t.add("summary", summary); + t.add("details1", details1); + t.add("details2", details2); + t.add("errors", String.join("\r\n", errors)); + + TextFile.stringToFile(t.render(), Utilities.path(path, "qa-dep.html")); + } + + public void addCodeSystem(String url, String title, String link) { + codeSystems.add(url); + triples.put(url, new Triple(url, title, link)); + } + + public void addValueSet(String url, String title, String link) { + valueSets.add(url); + triples.put(url, new Triple(url, title, link)); + } + + public void addProfile(String url, String title, String link) { + profiles.add(url); + triples.put(url, new Triple(url, title, link)); + } + + public void addExtension(String url, String title, String link) { + extensions.add(url); + triples.put(url, new Triple(url, title, link)); + } + + public void addLogical(String url, String title, String link) { + logicals.add(url); + triples.put(url, new Triple(url, title, link)); + } + + public void addSearchParam(String url, String title, String link) { + searchParams.add(url); + triples.put(url, new Triple(url, title, link)); + } + + public void addCapabilityStatement(String url, String title, String link) { + capabilityStatements.add(url); + triples.put(url, new Triple(url, title, link)); + } + + public void addExample(String url, String title, String link) { + examples.add(url); + triples.put(url, new Triple(url, title, link)); + } + + public String getCountDesc() { + return countDesc; + } + + +} diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/HTLMLInspector.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/HTLMLInspector.java index d5d0e3dd5..e3f082da3 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/HTLMLInspector.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/HTLMLInspector.java @@ -619,10 +619,18 @@ private boolean checkResolveLink(String filename, Location loc, String path, Str "http://hl7.org/fhir-issues", "http://hl7.org/registry") || matchesTarget(ref, "http://hl7.org", "http://hl7.org/fhir/DSTU2", "http://hl7.org/fhir/STU3", "http://hl7.org/fhir/R4", "http://hl7.org/fhir/smart-app-launch", "http://hl7.org/fhir/validator"); + // a local file may have bee created by some poorly tracked process, so we'll consider that as a possible + if (!resolved && !Utilities.isAbsoluteUrl(rref) && !rref.contains("..")) { // .. is security check. Maybe there's some ways it could be valid, but we're not interested for now + String fname = Utilities.path(new File(filename).getParent(), rref); + if (new File(fname).exists()) { + resolved = true; + } + } + // external terminology resources - if (!resolved) { - resolved = Utilities.startsWithInList(ref, "http://cts.nlm.nih.gov/fhir"); - } + if (!resolved) { + resolved = Utilities.startsWithInList(ref, "http://cts.nlm.nih.gov/fhir"); + } if (!resolved) { if (rref.startsWith("http://") || rref.startsWith("https://") || rref.startsWith("ftp://") || rref.startsWith("tel:")) { @@ -753,7 +761,7 @@ private boolean checkResolveImageLink(String filename, Location loc, String path if (resolved) return false; else { - messages.add(new ValidationMessage(Source.Publisher, IssueType.NOTFOUND, filename+(path == null ? "" : "#"+path+(loc == null ? "" : " at "+loc.toString())), "The image source '"+ref+"' cannot be resolved"+tgtList, IssueSeverity.ERROR).setLocationLink(uuid == null ? null : filename+"#"+uuid)); + messages.add(new ValidationMessage(Source.LinkChecker, IssueType.NOTFOUND, filename+(path == null ? "" : "#"+path+(loc == null ? "" : " at "+loc.toString())), "The image source '"+ref+"' cannot be resolved"+tgtList, IssueSeverity.ERROR).setLocationLink(uuid == null ? null : filename+"#"+uuid)); return true; } } diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/PreviousVersionComparator.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/PreviousVersionComparator.java index 54fa1c2dc..901c71b13 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/PreviousVersionComparator.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/PreviousVersionComparator.java @@ -105,10 +105,10 @@ public PreviousVersionComparator(SimpleWorkerContext context, String version, St private void processVersions(String canonical, List versions, String rootDir) throws IOException { JsonArray publishedVersions = null; for (String v : versions) { + if (publishedVersions == null) { + publishedVersions = fetchVersionHistory(canonical); + } if (Utilities.existsInList(v, "{last}", "{current}")) { - if (publishedVersions == null) { - publishedVersions = fetchVersionHistory(canonical); - } String last = null; String major = null; for (JsonElement e : publishedVersions) { diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/Publisher.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/Publisher.java index 69704290b..39b4f0ce4 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/Publisher.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/Publisher.java @@ -124,6 +124,7 @@ import org.hl7.fhir.igtools.renderers.XmlXHtmlRenderer; import org.hl7.fhir.igtools.spreadsheets.IgSpreadsheetParser; import org.hl7.fhir.igtools.spreadsheets.MappingSpace; +import org.hl7.fhir.igtools.spreadsheets.ObservationSummarySpreadsheetGenerator; import org.hl7.fhir.igtools.templates.Template; import org.hl7.fhir.igtools.templates.TemplateManager; import org.hl7.fhir.igtools.ui.GraphicalPublisher; @@ -640,6 +641,7 @@ public enum GenerationTool { private String qaDir; private String version; private FhirPublication pubVersion; + private long jekyllTimeout = JEKYLL_TIMEOUT; private long fshTimeout = FSH_TIMEOUT; private SuppressedMessageInformation suppressedMessages = new SuppressedMessageInformation(); @@ -820,6 +822,7 @@ public enum GenerationTool { private String fmtDateTime = "yyyy-MM-dd hh:mm:ssZZZ"; private String fmtDate = "yyyy-MM-dd"; + private DependentIGFinder dependentIgFinder; private class PreProcessInfo { private String xsltName; @@ -879,11 +882,12 @@ public void execute() throws Exception { long endTime = System.nanoTime(); processTxLog(Utilities.path(destDir != null ? destDir : outputDir, "qa-tx.html")); BallotChecker bc = new BallotChecker(repoRoot); + dependentIgFinder.finish(outputDir, sourceIg.present()); ValidationPresenter val = new ValidationPresenter(version, workingVersion(), igpkp, childPublisher == null? null : childPublisher.getIgpkp(), outputDir, npmName, childPublisher == null? null : childPublisher.npmName, bc.check(igpkp.getCanonical(), npmName, workingVersion(), historyPage, version), IGVersionUtil.getVersion(), fetchCurrentIGPubVersion(), realmRules, previousVersionComparator, new DependencyRenderer(pcm, outputDir, npmName, templateManager).render(publishedIg), new HTAAnalysisRenderer(context, outputDir, markdownEngine).render(publishedIg.getPackageId(), fileList, publishedIg.present()), new VersionCheckRenderer(npm.version(), publishedIg.getVersion(), bc.getPackageList(), igpkp.getCanonical()).generate(), copyrightYear, context, scanForR5Extensions(), - noNarrativeResources, noValidateResources, noValidation, noGenerate); + noNarrativeResources, noValidateResources, noValidation, noGenerate, dependentIgFinder); log("Built. "+Utilities.presentDuration(endTime - startTime)+". Validation output in "+val.generate(sourceIg.getName(), errors, fileList, Utilities.path(destDir != null ? destDir : outputDir, "qa.html"), suppressedMessages)); recordOutcome(null, val); log("Finished"); @@ -1019,11 +1023,12 @@ public void createIg() throws Exception, IOException, EOperationOutcome, FHIRExc generate(); clean(); BallotChecker bc = new BallotChecker(repoRoot); + dependentIgFinder.finish(outputDir, sourceIg.present()); ValidationPresenter val = new ValidationPresenter(version, workingVersion(), igpkp, childPublisher == null? null : childPublisher.getIgpkp(), rootDir, npmName, childPublisher == null? null : childPublisher.npmName, bc.check(igpkp.getCanonical(), npmName, workingVersion(), historyPage, version), IGVersionUtil.getVersion(), fetchCurrentIGPubVersion(), realmRules, previousVersionComparator, new DependencyRenderer(pcm, outputDir, npmName, templateManager).render(publishedIg), new HTAAnalysisRenderer(context, outputDir, markdownEngine).render(publishedIg.getPackageId(), fileList, publishedIg.present()), new VersionCheckRenderer(npm.version(), publishedIg.getVersion(), bc.getPackageList(), igpkp.getCanonical()).generate(), copyrightYear, context, scanForR5Extensions(), - noNarrativeResources, noValidateResources, noValidation, noGenerate); + noNarrativeResources, noValidateResources, noValidation, noGenerate, dependentIgFinder); tts.end(); if (isChild()) { log("Built. "+tt.report()); @@ -2507,7 +2512,7 @@ private void initializeFromIg(IniFile ini) throws Exception { if (comparisonVersions == null) { comparisonVersions = new ArrayList<>(); } - if ("n/a".equals(p.getValue())) { + if (!"n/a".equals(p.getValue())) { comparisonVersions.add(p.getValue()); } } else if (p.getCode().equals("validation")) { @@ -2525,6 +2530,10 @@ else if (p.getValue().equals("show-reference-messages")) count++; } + if (ini.hasProperty("IG", "jekyll-timeout")) { //todo: consider adding this to ImplementationGuideDefinitionParameterComponent + jekyllTimeout = ini.getLongProperty("IG", "jekyll-timeout") * 1000; + } + // ok process the paths log("Root directory: "+rootDir); if (resourceDirs.isEmpty()) @@ -2634,8 +2643,21 @@ else if (vsCache == null) { } fetcher.setPkp(igpkp); template.loadSummaryRows(igpkp.summaryRows()); +// if (!dependsOnToolsIg(sourceIg.getDependsOn())) { +// ImplementationGuideDependsOnComponent dep = new ImplementationGuideDependsOnComponent(); +// dep.setId("hl7tools"); +// dep.setPackageId(getUTGPackageName()); +// dep.setUri("http://hl7.org/fhir/tools"); +// dep.setVersion(pcm.getLatestVersion(dep.getPackageId())); +// sourceIg.getDependsOn().add(0, dep); +// } if (!dependsOnUTG(sourceIg.getDependsOn()) && !sourceIg.getPackageId().contains("hl7.terminology")) { - loadUTG(); + ImplementationGuideDependsOnComponent dep = new ImplementationGuideDependsOnComponent(); + dep.setId("hl7tx"); + dep.setPackageId(getUTGPackageName()); + dep.setUri("http://terminology.hl7.org/ImplementationGuide/hl7.terminology"); + dep.setVersion(pcm.getLatestVersion(dep.getPackageId())); + sourceIg.getDependsOn().add(0, dep); } inspector = new HTLMLInspector(outputDir, specMaps, this, igpkp.getCanonical(), sourceIg.getPackageId()); @@ -3206,23 +3228,29 @@ private void loadPubPack() throws FHIRException, IOException { } private void loadUTG() throws FHIRException, IOException { + String vs = getUTGPackageName(); + if (vs != null) { + NpmPackage npm = pcm.loadPackage(vs, null); + SpecMapManager spm = new SpecMapManager(TextFile.streamToBytes(npm.load("other", "spec.internals")), npm.fhirVersion()); + IContextResourceLoader loader = new PublisherLoader(npm, spm, npm.getWebLocation(), igpkp).makeLoader(); + context.loadFromPackage(npm, loader); + } + } + + private String getUTGPackageName() throws FHIRException, IOException { String vs = null; if (VersionUtilities.isR3Ver(version)) { vs = "hl7.terminology.r3"; - } else if (VersionUtilities.isR4Ver(version)) { + } else if (VersionUtilities.isR4Ver(version) || VersionUtilities.isR4BVer(version)) { vs = "hl7.terminology.r4"; } else if (VersionUtilities.isR5Ver(version)) { vs = "hl7.terminology.r5"; } - if (vs != null) { - NpmPackage npm = pcm.loadPackage(vs, null); - SpecMapManager spm = new SpecMapManager(TextFile.streamToBytes(npm.load("other", "spec.internals")), npm.fhirVersion()); - IContextResourceLoader loader = new PublisherLoader(npm, spm, npm.getWebLocation(), igpkp).makeLoader(); - context.loadFromPackage(npm, loader); - } + return vs; } + private void processExtraTemplates(JsonArray templates) throws Exception { if (templates!=null) { boolean hasDefns = false; // is definitions page in list of templates? @@ -3881,7 +3909,9 @@ private boolean load() throws Exception { publishedIg = sourceIg.copy(); altMap.get(IG_NAME).getResources().get(0).setResource(publishedIg); } - + dependentIgFinder = new DependentIGFinder(sourceIg.getPackageId()); + + loadMappingSpaces(context.getBinaries().get("mappingSpaces.details")); validationFetcher.getMappingUrls().addAll(mappingSpaces.keySet()); validationFetcher.getOtherUrls().add(publishedIg.getUrl()); @@ -4131,6 +4161,7 @@ private boolean load() throws Exception { logDebugMessage(LogCategory.INIT, " "+r.fhirType()+"/"+r.getId()); } extensionTracker.scan(publishedIg); + return needToBuild; } @@ -4176,6 +4207,7 @@ private void templateBeforeGenerate() throws IOException, FHIRException { if (debug) { waitForInput("before OnGenerate"); } + logMessage("Run Template"); Session tts = tt.start("template"); List newFileList = new ArrayList(); checkOutcomes(template.beforeGenerateEvent(publishedIg, tempDir, otherFilesRun, newFileList)); @@ -4200,6 +4232,7 @@ private void templateBeforeGenerate() throws IOException, FHIRException { loadIgPages(publishedIg.getDefinition().getPage(), igPages); } tts.end(); + logMessage("Template Done"); if (debug) { waitForInput("after OnGenerate"); } @@ -4620,6 +4653,11 @@ private String tail(String url) { return url.substring(url.lastIndexOf("/")+1); } + private String tailPI(String url) { + int i = url.contains("\\") ? url.lastIndexOf("\\") : url.lastIndexOf("/"); + return url.substring(i+1); + } + private List metadataResourceNames() { List res = new ArrayList<>(); // order matters here @@ -4666,6 +4704,7 @@ private List metadataResourceNames() { private void loadConformance() throws Exception { for (String s : metadataResourceNames()) scan(s); + loadDepInfo(); loadInfo(); for (String s : metadataResourceNames()) load(s); @@ -5176,14 +5215,62 @@ private void scan(String type) throws Exception { logDebugMessage(LogCategory.PROGRESS, "process type: "+type); for (FetchedFile f : fileList) { for (FetchedResource r : f.getResources()) { - if (r.fhirType().equals(type)) { - String url = r.getElement().getChildValue("url"); - if (url != null) { + String url = r.getElement().getChildValue("url"); + if (url != null) { + String title = r.getElement().getChildValue("title"); + if (title == null) { + title = r.getElement().getChildValue("name"); + } + String link = igpkp.getLinkFor(r, true); + if (r.fhirType().equals(type)) { validationFetcher.getOtherUrls().add(url); } } } } + + } + + private void loadDepInfo() { + for (FetchedFile f : fileList) { + for (FetchedResource r : f.getResources()) { + String url = r.getElement().getChildValue("url"); + if (url != null) { + String title = r.getElement().getChildValue("title"); + if (title == null) { + title = r.getElement().getChildValue("name"); + } + String link = igpkp.getLinkFor(r, true); + switch (r.fhirType() ) { + case "CodeSystem": + dependentIgFinder.addCodeSystem(url, title, link); + break; + case "ValueSet": + dependentIgFinder.addValueSet(url, title, link); + break; + case "StructureDefinition": + String kind = r.getElement().getChildValue("url"); + if ("logical".equals(kind)) { + dependentIgFinder.addLogical(url, title, link); + } else if ("Extension".equals(r.getElement().getChildValue("type"))) { + dependentIgFinder.addExtension(url, title, link); + } else { + dependentIgFinder.addProfile(url, title, link); + } + break; + case "SearchParameter": + dependentIgFinder.addSearchParam(url, title, link); + break; + case "CapabilityStatement": + dependentIgFinder.addCapabilityStatement(url, title, link); + break; + default: + // do nothing + } + } + } + } + dependentIgFinder.go(); } private void loadLists() throws Exception { @@ -6648,7 +6735,7 @@ private boolean runJekyll() throws IOException, InterruptedException { PumpStreamHandler pump = new PumpStreamHandler(pumpHandler, pumpHandler); exec.setStreamHandler(pump); exec.setWorkingDirectory(new File(tempDir)); - ExecuteWatchdog watchdog = new ExecuteWatchdog(JEKYLL_TIMEOUT); + ExecuteWatchdog watchdog = new ExecuteWatchdog(jekyllTimeout); exec.setWatchdog(watchdog); try { @@ -6672,13 +6759,13 @@ private boolean runJekyll() throws IOException, InterruptedException { tts.end(); if (pumpHandler.observedToSucceed) { if (watchdog.killedProcess()) { - log("Jekyll timeout exceeded: " + Long.toString(JEKYLL_TIMEOUT/1000) + " seconds"); + log("Jekyll timeout exceeded: " + Long.toString(jekyllTimeout/1000) + " seconds"); } log("Jekyll claimed to succeed, but returned an error. Proceeding anyway"); } else { log("Jekyll has failed. Complete output from running Jekyll: " + pumpHandler.getBufferString()); if (watchdog.killedProcess()) { - log("Jekyll timeout exceeded: " + Long.toString(JEKYLL_TIMEOUT/1000) + " seconds"); + log("Jekyll timeout exceeded: " + Long.toString(jekyllTimeout/1000) + " seconds"); } else { log("Note: Check that Jekyll is installed correctly"); } @@ -6706,7 +6793,7 @@ private void generateSummaryOutputs() throws Exception { generateDataFile(); generateCanonicalSummary(); - CrossViewRenderer cvr = new CrossViewRenderer(igpkp.getCanonical(), context); + CrossViewRenderer cvr = new CrossViewRenderer(igpkp.getCanonical(), context, igpkp.specPath()); for (FetchedFile f : fileList) { for (FetchedResource r : f.getResources()) { if (r.getResource() != null && r.getResource() instanceof CanonicalResource) { @@ -6715,6 +6802,12 @@ private void generateSummaryOutputs() throws Exception { } } fragment("summary-observations", cvr.getObservationSummary(), otherFilesRun); + String path = Utilities.path(tempDir, "observations-summary.xlsx"); + ObservationSummarySpreadsheetGenerator vsg = new ObservationSummarySpreadsheetGenerator(context); + otherFilesRun.add(path); + vsg.generate(cvr.getObservations()); + vsg.finish(new FileOutputStream(path)); + fragment("summary-extensions", cvr.getExtensionSummary(), otherFilesRun); fragment("ip-statements", genIpStatements(fileList), otherFilesRun); @@ -6740,7 +6833,9 @@ private void generateSummaryOutputs() throws Exception { item.addProperty("name", sd.getName()); item.addProperty("title", sd.present()); item.addProperty("path", sd.getUserString("path")); - item.addProperty("kind", sd.getKind().toCode()); + if (sd.hasKind()) { + item.addProperty("kind", sd.getKind().toCode()); + } item.addProperty("type", sd.getType()); item.addProperty("base", sd.getBaseDefinition()); StructureDefinition base = sd.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()) : null; @@ -6751,8 +6846,12 @@ private void generateSummaryOutputs() throws Exception { item.addProperty("basename", "Base"); item.addProperty("basepath", "http://hl7.org/fhir/StructureDefinition/Element"); } - item.addProperty("status", sd.getStatus().toCode()); - item.addProperty("date", sd.getDate().toString()); + if (sd.hasStatus()) { + item.addProperty("status", sd.getStatus().toCode()); + } + if (sd.hasDate()) { + item.addProperty("date", sd.getDate().toString()); + } item.addProperty("abstract", sd.getAbstract()); if (sd.hasDerivation()) { item.addProperty("derivation", sd.getDerivation().toCode()); @@ -6830,7 +6929,8 @@ private void generateSummaryOutputs() throws Exception { item.addProperty("history", r.hasHistory()); item.addProperty("index", i); item.addProperty("source", f.getStatedPath()); - String path = null; + item.addProperty("sourceTail", tailPI(f.getStatedPath())); + path = null; if (r.getPath() != null) { path = r.getPath(); } else if (r.getResource() != null) { @@ -6863,6 +6963,7 @@ private void generateSummaryOutputs() throws Exception { citem.addProperty("history", r.hasHistory()); citem.addProperty("index", i); citem.addProperty("source", f.getStatedPath()+"#"+crd.getId()); + citem.addProperty("sourceTail", tailPI(f.getStatedPath())+"#"+crd.getId()); citem.addProperty("path", crd.getType()+"-"+r.getId()+"_"+crd.getId()+".html");// todo: is this always correct? JsonObject container = new JsonObject();; citem.add("container", container); diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/CrossViewRenderer.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/CrossViewRenderer.java index 8015ceb25..e2eee5014 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/CrossViewRenderer.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/CrossViewRenderer.java @@ -5,6 +5,7 @@ import java.util.Comparator; import java.util.List; +import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; import org.hl7.fhir.r5.model.CanonicalResource; @@ -12,15 +13,27 @@ import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.renderers.DataRenderer; import org.hl7.fhir.r5.renderers.TerminologyRenderer; +import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationOptions; public class CrossViewRenderer { + public class UsedType { + public UsedType(String name, boolean ms) { + super(); + this.name = name; + this.ms = ms; + } + public String name; + public boolean ms; + } + public class ObsListSorter implements Comparator { @Override public int compare(ObservationProfile l, ObservationProfile r) { @@ -38,42 +51,74 @@ public int compare(ExtensionDefinition l, ExtensionDefinition r) { public class StructureDefinitionNote { public StructureDefinition source; } - + public class ObservationProfile extends StructureDefinitionNote { public List code = new ArrayList<>(); + public ElementDefinitionBindingComponent codeVS; public List category = new ArrayList<>(); - public List effectiveTypes = new ArrayList<>(); - public List types = new ArrayList<>(); - public boolean dataAbsentReason; + public ElementDefinitionBindingComponent catVS; + public List effectiveTypes = new ArrayList<>(); + public List types = new ArrayList<>(); + public Boolean dataAbsentReason; public List bodySite = new ArrayList<>(); public List method = new ArrayList<>(); public List components = new ArrayList<>(); public List members = new ArrayList(); - + public String name; + public boolean hasValue() { return code.size() > 0 || category.size() > 0; } } - + public class ExtensionDefinition extends StructureDefinitionNote { public String code; public String definition; - public List types = new ArrayList<>(); + public List types = new ArrayList<>(); public List components = new ArrayList<>(); } - + private String canonical; private IWorkerContext context; private List obsList = new ArrayList<>(); private List extList = new ArrayList<>(); - - - public CrossViewRenderer(String canonical, IWorkerContext context) { + public List baseEffectiveTypes = new ArrayList<>(); + public List baseTypes = new ArrayList<>(); + public List baseExtTypes = new ArrayList<>(); + public String corePath; + + public CrossViewRenderer(String canonical, IWorkerContext context, String corePath) { super(); this.canonical = canonical; this.context = context; + this.corePath = corePath; + getBaseTypes(); } - + + private void getBaseTypes() { + StructureDefinition sd = context.fetchTypeDefinition("Observation"); + for (ElementDefinition ed : sd.getSnapshot().getElement()) { + if (ed.getPath().equals("Observation.effective[x]")) { + for (TypeRefComponent tr : ed.getType()) + if (!baseEffectiveTypes.contains(tr.getWorkingCode())) + baseEffectiveTypes.add(tr.getWorkingCode()); + } + if (ed.getPath().startsWith("Observation.value") && Utilities.charCount(ed.getPath(), '.') == 1 && !ed.getMax().equals("0")) { + for (TypeRefComponent tr : ed.getType()) + if (!baseTypes.contains(tr.getWorkingCode())) + baseTypes.add(tr.getWorkingCode()); + } + } + sd = context.fetchTypeDefinition("Extension"); + for (ElementDefinition ed : sd.getSnapshot().getElement()) { + if (ed.getPath().startsWith("Extension.value") && !ed.getMax().equals("0")) { + for (TypeRefComponent tr : ed.getType()) + if (!baseExtTypes.contains(tr.getCode())) + baseExtTypes.add(tr.getCode()); + } + } + } + public void seeResource(CanonicalResource res) { if (res instanceof StructureDefinition) { seeStructureDefinition((StructureDefinition) res); @@ -91,7 +136,7 @@ public void seeStructureDefinition(StructureDefinition sd) { private void checkForExtensions(StructureDefinition sd) { // TODO Auto-generated method stub - + } private void seeObservation(StructureDefinition sd) { @@ -99,89 +144,110 @@ private void seeObservation(StructureDefinition sd) { obs.source = sd; int i = 0; String system = null; + String compSlice = null; while (i < sd.getSnapshot().getElement().size()) { ElementDefinition ed = sd.getSnapshot().getElement().get(i); - - if (ed.getPath().equals("Observation.category") && ed.hasFixed() && ed.getFixed() instanceof CodeableConcept) { - obs.category.addAll(((CodeableConcept) ed.getFixed()).getCoding()); + + if (ed.getPath().equals("Observation.category") && ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof CodeableConcept) { + obs.category.addAll(((CodeableConcept) ed.getFixedOrPattern()).getCoding()); } if (ed.getPath().equals("Observation.category.coding")) { system = null; - if (ed.hasFixed() && ed.getFixed() instanceof Coding) { - obs.category.add(((Coding) ed.getFixed())); + if (ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof Coding) { + obs.category.add(((Coding) ed.getFixedOrPattern())); + } else if (ed.getBinding().hasValueSet()) { + obs.catVS = ed.getBinding(); + } else if (ed.getBinding().hasValueSet()) { + obs.catVS = ed.getBinding(); } } - if (ed.getPath().equals("Observation.category.coding.system") && ed.hasFixed()) { - system = ed.getFixed().primitiveValue(); + if (ed.getPath().equals("Observation.category.coding.system") && ed.hasFixedOrPattern()) { + system = ed.getFixedOrPattern().primitiveValue(); } - if (ed.getPath().equals("Observation.category.coding.code") && (system != null && ed.hasFixed())) { - obs.category.add(new Coding(system, ed.getFixed().primitiveValue(), null)); + if (ed.getPath().equals("Observation.category.coding.code") && (system != null && ed.hasFixedOrPattern())) { + obs.category.add(new Coding(system, ed.getFixedOrPattern().primitiveValue(), null)); system = null; } - - if (ed.getPath().equals("Observation.code") && ed.hasFixed() && ed.getFixed() instanceof CodeableConcept) { - obs.code.addAll(((CodeableConcept) ed.getFixed()).getCoding()); - } + + if (ed.getPath().equals("Observation.code")) { + if (ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof CodeableConcept) { + obs.code.addAll(((CodeableConcept) ed.getFixedOrPattern()).getCoding()); + } else if (ed.getBinding().hasValueSet()) { + obs.codeVS = ed.getBinding(); + } + } if (ed.getPath().equals("Observation.code.coding")) { system = null; - if (ed.hasFixed() && ed.getFixed() instanceof Coding) { - obs.code.add(((Coding) ed.getFixed())); + if (ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof Coding) { + obs.code.add(((Coding) ed.getFixedOrPattern())); + } else if (ed.getBinding().hasValueSet()) { + obs.codeVS = ed.getBinding(); } } - if (ed.getPath().equals("Observation.code.coding.system") && ed.hasFixed()) { - system = ed.getFixed().primitiveValue(); + if (ed.getPath().equals("Observation.code.coding.system") && ed.hasFixedOrPattern()) { + system = ed.getFixedOrPattern().primitiveValue(); } - if (ed.getPath().equals("Observation.code.coding.code") && (system != null && ed.hasFixed())) { - obs.code.add(new Coding(system, ed.getFixed().primitiveValue(), null)); + if (ed.getPath().equals("Observation.code.coding.code") && (system != null && ed.hasFixedOrPattern())) { + obs.code.add(new Coding(system, ed.getFixedOrPattern().primitiveValue(), null)); system = null; } - if (ed.getPath().equals("Observation.bodySite") && ed.hasFixed() && ed.getFixed() instanceof CodeableConcept) { - obs.bodySite.addAll(((CodeableConcept) ed.getFixed()).getCoding()); + if (ed.getPath().equals("Observation.bodySite") && ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof CodeableConcept) { + obs.bodySite.addAll(((CodeableConcept) ed.getFixedOrPattern()).getCoding()); } if (ed.getPath().equals("Observation.bodySite.coding")) { system = null; - if (ed.hasFixed() && ed.getFixed() instanceof Coding) { - obs.bodySite.add(((Coding) ed.getFixed())); + if (ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof Coding) { + obs.bodySite.add(((Coding) ed.getFixedOrPattern())); } } - if (ed.getPath().equals("Observation.bodySite.coding.system") && ed.hasFixed()) { - system = ed.getFixed().primitiveValue(); + if (ed.getPath().equals("Observation.bodySite.coding.system") && ed.hasFixedOrPattern()) { + system = ed.getFixedOrPattern().primitiveValue(); } - if (ed.getPath().equals("Observation.bodySite.coding.code") && (system != null && ed.hasFixed())) { - obs.bodySite.add(new Coding(system, ed.getFixed().primitiveValue(), null)); + if (ed.getPath().equals("Observation.bodySite.coding.code") && (system != null && ed.hasFixedOrPattern())) { + obs.bodySite.add(new Coding(system, ed.getFixedOrPattern().primitiveValue(), null)); system = null; } - if (ed.getPath().equals("Observation.method") && ed.hasFixed() && ed.getFixed() instanceof CodeableConcept) { - obs.method.addAll(((CodeableConcept) ed.getFixed()).getCoding()); + if (ed.getPath().equals("Observation.method") && ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof CodeableConcept) { + obs.method.addAll(((CodeableConcept) ed.getFixedOrPattern()).getCoding()); } if (ed.getPath().equals("Observation.method.coding")) { system = null; - if (ed.hasFixed() && ed.getFixed() instanceof Coding) { - obs.method.add(((Coding) ed.getFixed())); + if (ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof Coding) { + obs.method.add(((Coding) ed.getFixedOrPattern())); } } - if (ed.getPath().equals("Observation.method.coding.system") && ed.hasFixed()) { - system = ed.getFixed().primitiveValue(); + if (ed.getPath().equals("Observation.method.coding.system") && ed.hasFixedOrPattern()) { + system = ed.getFixedOrPattern().primitiveValue(); } - if (ed.getPath().equals("Observation.method.coding.code") && (system != null && ed.hasFixed())) { - obs.method.add(new Coding(system, ed.getFixed().primitiveValue(), null)); + if (ed.getPath().equals("Observation.method.coding.code") && (system != null && ed.hasFixedOrPattern())) { + obs.method.add(new Coding(system, ed.getFixedOrPattern().primitiveValue(), null)); system = null; } - + if (ed.getPath().equals("Observation.effective[x]")) { for (TypeRefComponent tr : ed.getType()) - if (!obs.effectiveTypes.contains(tr.getCode())) - obs.effectiveTypes.add(tr.getCode()); + if (!typesContain(obs.effectiveTypes, tr.getWorkingCode())) + obs.effectiveTypes.add(new UsedType(tr.getWorkingCode(), isMustSupport(ed, tr))); } - if (ed.getPath().startsWith("Observation.value") && !ed.getMax().equals("0")) { + if (ed.getPath().startsWith("Observation.value") && Utilities.charCount(ed.getPath(), '.') == 1 && !ed.getMax().equals("0")) { for (TypeRefComponent tr : ed.getType()) - if (!obs.types.contains(tr.getCode())) - obs.types.add(tr.getCode()); + if (!typesContain(obs.types, tr.getWorkingCode())) + obs.types.add(new UsedType(tr.getWorkingCode(), isMustSupport(ed, tr))); + } + if (ed.getPath().equals("Observation.dataAbsentReason")) { + if (ed.getMax().equals("0")) { + obs.dataAbsentReason = false; + } else if (ed.getMin() == 1) { + obs.dataAbsentReason = true; + } } - if (ed.getPath().startsWith("Observation.component.")) { - i = processObservationComponent(obs, sd.getSnapshot().getElement(), i); + if (ed.getPath().equals("Observation.component")) { + compSlice = ed.getSliceName(); + } + if (ed.getPath().startsWith("Observation.component.") && !ed.isProhibited() && compSlice != null) { + i = processObservationComponent(obs, sd.getSnapshot().getElement(), compSlice, i); } else { i++; } @@ -192,49 +258,63 @@ private void seeObservation(StructureDefinition sd) { } } - private int processObservationComponent(ObservationProfile parent, List list, int i) { + private boolean typesContain(List types, String name) { + for (UsedType t : types) { + if (t.name.equals(name)) { + return true; + } + } + return false; + } + + private boolean isMustSupport(ElementDefinition ed, TypeRefComponent tr) { + return ed.getMustSupport() || "true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT)); + } + + private int processObservationComponent(ObservationProfile parent, List list, String compSlice, int i) { ObservationProfile obs = new ObservationProfile(); String system = null; + obs.name = compSlice; while (i < list.size() && list.get(i).getPath().startsWith("Observation.component.")) { ElementDefinition ed = list.get(i); - if (ed.getPath().equals("Observation.component.category") && ed.hasFixed() && ed.getFixed() instanceof CodeableConcept) { - obs.category.addAll(((CodeableConcept) ed.getFixed()).getCoding()); + if (ed.getPath().equals("Observation.component.category") && ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof CodeableConcept) { + obs.category.addAll(((CodeableConcept) ed.getFixedOrPattern()).getCoding()); } if (ed.getPath().equals("Observation.component.category.coding")) { system = null; - if (ed.hasFixed() && ed.getFixed() instanceof Coding) { - obs.category.add(((Coding) ed.getFixed())); + if (ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof Coding) { + obs.category.add(((Coding) ed.getFixedOrPattern())); } } - if (ed.getPath().equals("Observation.component.category.coding.system") && ed.hasFixed()) { - system = ed.getFixed().primitiveValue(); + if (ed.getPath().equals("Observation.component.category.coding.system") && ed.hasFixedOrPattern()) { + system = ed.getFixedOrPattern().primitiveValue(); } - if (ed.getPath().equals("Observation.component.category.coding.code") && (system != null && ed.hasFixed())) { - obs.method.add(new Coding(system, ed.getFixed().primitiveValue(), null)); + if (ed.getPath().equals("Observation.component.category.coding.code") && (system != null && ed.hasFixedOrPattern())) { + obs.method.add(new Coding(system, ed.getFixedOrPattern().primitiveValue(), null)); system = null; } - - if (ed.getPath().equals("Observation.component.code") && ed.hasFixed() && ed.getFixed() instanceof CodeableConcept) { - obs.code.addAll(((CodeableConcept) ed.getFixed()).getCoding()); + + if (ed.getPath().equals("Observation.component.code") && ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof CodeableConcept) { + obs.code.addAll(((CodeableConcept) ed.getFixedOrPattern()).getCoding()); } if (ed.getPath().equals("Observation.component.code.coding")) { system = null; - if (ed.hasFixed() && ed.getFixed() instanceof Coding) { - obs.code.add(((Coding) ed.getFixed())); + if (ed.hasFixedOrPattern() && ed.getFixedOrPattern() instanceof Coding) { + obs.code.add(((Coding) ed.getFixedOrPattern())); } } - if (ed.getPath().equals("Observation.component.code.coding.system") && ed.hasFixed()) { - system = ed.getFixed().primitiveValue(); + if (ed.getPath().equals("Observation.component.code.coding.system") && ed.hasFixedOrPattern()) { + system = ed.getFixedOrPattern().primitiveValue(); } - if (ed.getPath().equals("Observation.component.code.coding.code") && (system != null && ed.hasFixed())) { - obs.method.add(new Coding(system, ed.getFixed().primitiveValue(), null)); + if (ed.getPath().equals("Observation.component.code.coding.code") && (system != null && ed.hasFixedOrPattern())) { + obs.method.add(new Coding(system, ed.getFixedOrPattern().primitiveValue(), null)); system = null; } - - if (ed.getPath().equals("Observation.component.value") && !ed.getMax().equals("0")) { + + if (ed.getPath().startsWith("Observation.component.value") && Utilities.charCount(ed.getPath(), '.') == 2 && !ed.getMax().equals("0")) { for (TypeRefComponent tr : ed.getType()) - if (!obs.types.contains(tr.getCode())) - obs.types.add(tr.getCode()); + if (!typesContain(obs.types, tr.getWorkingCode())) + obs.types.add(new UsedType(tr.getWorkingCode(), isMustSupport(ed, tr))); } i++; } @@ -257,8 +337,8 @@ private void seeExtensionDefinition(StructureDefinition sd) { ElementDefinition ed = sd.getSnapshot().getElement().get(i); if (ed.getPath().startsWith("Extension.value") && !ed.getMax().equals("0")) { for (TypeRefComponent tr : ed.getType()) - if (!exd.types.contains(tr.getCode())) - exd.types.add(tr.getCode()); + if (!typesContain(exd.types, tr.getCode())) + exd.types.add(new UsedType(tr.getCode(), isMustSupport(ed, tr))); } if (ed.getPath().startsWith("Extension.extension.")) { i = processExtensionComponent(exd, sd.getSnapshot().getElement(), sd.getSnapshot().getElement().get(i-1).getDefinition(), i); @@ -273,8 +353,8 @@ private int processExtensionComponent(ExtensionDefinition parent, List canonical.length() + 21) { System.out.println("extension code doesn't follow canonical pattern: "+exd.code); @@ -283,11 +363,11 @@ private int processExtensionComponent(ExtensionDefinition parent, ListValue Types"); b.append("Definition"); b.append("\r\n"); - + for (ExtensionDefinition op : extList) { b.append(" "); b.append("
"+op.code+""); - renderTypeCell(b, true, op.types); + renderTypeCell(b, true, op.types, baseExtTypes); b.append(""+Utilities.escapeXml(op.definition)+""); b.append("\r\n"); for (ExtensionDefinition inner : op.components) { b.append(" "); b.append("  "+inner.code+""); - renderTypeCell(b, true, inner.types); + renderTypeCell(b, true, inner.types, baseExtTypes); b.append(""+Utilities.escapeXml(inner.definition)+""); b.append("\r\n"); - + } } b.append("\r\n"); } return b.toString(); } - + public String getObservationSummary() { StringBuilder b = new StringBuilder(); @@ -339,21 +419,27 @@ public String getObservationSummary() { b.append("\r\n"); boolean hasCat = false; boolean hasCode = false; - boolean hasLoinc = false; - boolean hasSnomed = false; boolean hasEffective = false; boolean hasTypes = false; + boolean hasDAR = false; boolean hasBodySite = false; boolean hasMethod = false; for (ObservationProfile op : obsList) { - hasCat = hasCat || !op.category.isEmpty(); - hasLoinc = hasLoinc || hasCode(op.code, "http://loinc.org"); - hasSnomed = hasSnomed || hasCode(op.code, "http://snomed.info/sct"); - hasCode = hasCode || hasOtherCode(op.code, "http://loinc.org", "http://snomed.info/sct"); + hasCat = hasCat || !op.category.isEmpty() || op.catVS != null; + hasCode = hasCode || !op.code.isEmpty() | op.codeVS != null; hasEffective = hasEffective || !op.effectiveTypes.isEmpty(); hasTypes = hasTypes || !op.types.isEmpty(); hasBodySite = hasBodySite || !op.bodySite.isEmpty(); hasMethod = hasMethod || !op.method.isEmpty(); + hasDAR = hasDAR || op.dataAbsentReason != null; + for (ObservationProfile op2 : op.components) { + hasCode = hasCode || !op2.code.isEmpty() | op2.codeVS != null; + hasEffective = hasEffective || !op2.effectiveTypes.isEmpty(); + hasTypes = hasTypes || !op2.types.isEmpty(); + hasBodySite = hasBodySite || !op2.bodySite.isEmpty(); + hasMethod = hasMethod || !op2.method.isEmpty(); + hasDAR = hasDAR || op2.dataAbsentReason != null; + } } if (hasCat) { List cat = obsList.get(0).category; @@ -373,37 +459,47 @@ public String getObservationSummary() { } } b.append(" "); + b.append(""); if (hasCat) b.append(""); - if (hasLoinc) b.append(""); - if (hasSnomed) b.append(""); if (hasCode) b.append(""); - b.append(""); if (hasEffective) b.append(""); if (hasTypes) b.append(""); + if (hasDAR) b.append(""); if (hasBodySite) b.append(""); if (hasMethod) b.append(""); b.append("\r\n"); - + for (ObservationProfile op : obsList) { b.append(" "); - renderCodingCell(b, hasCat, op.category, null, null); - renderCodingCell(b, hasLoinc, op.code, "http://loinc.org", "https://loinc.org/"); - renderCodingCell(b, hasSnomed, op.code, "http://snomed.info/sct", null); - renderCodingCell(b, hasCode, op.code, null, null); - b.append(""); - renderTypeCell(b, hasEffective, op.effectiveTypes); - renderTypeCell(b, hasTypes, op.types); - renderCodingCell(b, hasBodySite, op.bodySite, null, null); - renderCodingCell(b, hasMethod, op.method, null, null); - + b.append(""); + renderCodingCell(b, hasCat, op.category, op.catVS); + renderCodingCell(b, hasCode, op.code, op.codeVS); + renderTypeCell(b, hasEffective, op.effectiveTypes, baseEffectiveTypes); + renderTypeCell(b, hasTypes, op.types, baseTypes); + renderBoolean(b, hasDAR, op.dataAbsentReason); + renderCodingCell(b, hasBodySite, op.bodySite, null); + renderCodingCell(b, hasMethod, op.method, null); b.append("\r\n"); + for (ObservationProfile op2 : op.components) { + b.append(" "); + b.append(""); + b.append(""); + renderCodingCell(b, hasCode, op2.code, op2.codeVS); + renderTypeCell(b, hasEffective, op2.effectiveTypes, baseEffectiveTypes); + renderTypeCell(b, hasTypes, op2.types, baseTypes); + renderBoolean(b, hasDAR, op2.dataAbsentReason); + renderCodingCell(b, hasBodySite, op2.bodySite, null); + renderCodingCell(b, hasMethod, op2.method, null); + b.append("\r\n"); + } } b.append("
Profile NameCategoryLoincSnomedCodeProfile NameTime TypesValue TypesData Absent ReasonBody SiteMethod
"+op.source.present()+""+op.source.getId()+"
  "+op2.name+"
\r\n"); } return b.toString(); } - + + private boolean isSameCodes(List l1, List l2) { if (l1.size() != l2.size()) return false; @@ -418,48 +514,89 @@ private boolean isSameCodes(List l1, List l2) { return true; } - private boolean hasOtherCode(List codes, String... exclusions) { - boolean found = false; - for (Coding c : codes) { - if (!Utilities.existsInList(c.getSystem(), exclusions)) { - found = true; - } - } - return found; - } - - private boolean hasCode(List codes, String system) { - for (Coding c : codes) { - if (system.equals(c.getSystem())) { - return true; + private void renderBoolean(StringBuilder b, boolean render, Boolean bool) { + if (render) { + b.append(""); + if (bool == null) { + b.append(""); + } else if (bool) { + b.append(""); + } else { + b.append(""); } - } - return false; + b.append(""); + } } - private void renderTypeCell(StringBuilder b, boolean render, List types) { + private void renderTypeCell(StringBuilder b, boolean render, List types, List base) { if (render) { b.append(""); - boolean first = true; - for (String t : types) { - if (first) first = false; else b.append(" | "); - StructureDefinition sd = context.fetchTypeDefinition(t); - if (sd != null) { - b.append(""+t+""); - } else { - b.append(t); + if (types.size() == base.size() && allMSAreSame(types)) { + if (types.size() > 0 && types.get(0).ms) { + b.append(" S "); + } + b.append("(all)"); + } else { + boolean doMS = !allMSAreSame(types); + boolean first = true; + for (UsedType t : types) { + if (!doMS && first && t.ms) { + b.append(" S "); + } + if (first) first = false; else b.append(" | "); + StructureDefinition sd = context.fetchTypeDefinition(t.name); + if (sd != null) { + b.append(""+t.name+""); + } else { + b.append(t.name); + } + if (doMS && t.ms) { + b.append(" S"); + } } } b.append(""); + } + } + + private boolean allMSAreSame(List types) { + if (types.size() == 0) { + return false; + } + boolean ms = types.get(0).ms; + for (UsedType t : types) { + if (ms != t.ms) { + return false; + } } - + return true; } - private void renderCodingCell(StringBuilder b, boolean render, List list, String system, String link) { + + private boolean noneAreMs(List types) { + for (UsedType t : types) { + if (t.ms) { + return false; + } + } + return true; + } + + private void renderCodingCell(StringBuilder b, boolean render, List list, ElementDefinitionBindingComponent binding) { if (render) { b.append(""); boolean first = true; - for (Coding t : list) { - if (system == null || system.equals(t.getSystem())) { + if (binding != null) { + b.append(""+binding.getStrength().toCode()+" VS "); + ValueSet vs = context.fetchResource(ValueSet.class, binding.getValueSet()); + if (vs == null) { + b.append(Utilities.escapeXml(binding.getValueSet())); + } else if (vs.hasUserData("path")) { + b.append(""+Utilities.escapeXml(vs.present())+""); + } else { + b.append(Utilities.escapeXml(vs.present())); + } + } else { + for (Coding t : list) { if (first) first = false; else b.append(", "); String sys = TerminologyRenderer.describeSystem(t.getSystem()); if (sys.equals(t.getSystem())) @@ -469,30 +606,30 @@ private void renderCodingCell(StringBuilder b, boolean render, List list if (cs != null) sys = cs.getTitle(); } + t.setUserData("desc", sys); ValidationResult vr = context.validateCode(ValidationOptions.defaults(), t.getSystem(), t.getVersion(), t.getCode(), null); - if (system != null) { - if (vr != null & vr.getDisplay() != null) { - b.append(""+t.getCode()+""); + if (vr != null & vr.getDisplay() != null) { + // if (Utilities.existsInList(t.getSystem(), "http://loinc.org")) + // b.append(""+t.getCode()+" "+vr.getDisplay()+""); + // else { + CodeSystem cs = context.fetchCodeSystem(t.getSystem()); + if (cs != null && cs.hasUserData("path")) { + b.append(""+t.getCode()+""); } else { - b.append(""+t.getCode()+""); + b.append(""+t.getCode()+""); } + // } } else { - if (vr != null & vr.getDisplay() != null) { - if (Utilities.existsInList(t.getSystem(), "http://loinc.org")) - b.append(""+t.getCode()+" "+vr.getDisplay()+""); - else - b.append(""+t.getCode()+""); - } else { - b.append(""+t.getCode()+""); - } + b.append(""+t.getCode()+""); } } } b.append(""); } } - - private String gen(Coding t) { - return t.getSystem()+"#"+t.getCode(); + + public List getObservations() { + return obsList; } + } diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/ValidationPresenter.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/ValidationPresenter.java index c1fba19ee..52592f14c 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/ValidationPresenter.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/ValidationPresenter.java @@ -38,6 +38,7 @@ import java.util.Map.Entry; import java.util.Set; +import org.hl7.fhir.igtools.publisher.DependentIGFinder; import org.hl7.fhir.igtools.publisher.FetchedFile; import org.hl7.fhir.igtools.publisher.FetchedResource; import org.hl7.fhir.igtools.publisher.IGKnowledgeProvider; @@ -260,11 +261,13 @@ public ValidationMessage getVm() { private boolean noValidate; private boolean noGenerate; private Set r5Extensions; - + private String dependencies; + private DependentIGFinder dependentIgs; + public ValidationPresenter(String statedVersion, String igVersion, IGKnowledgeProvider provider, IGKnowledgeProvider altProvider, String root, String packageId, String altPackageId, String ballotCheck, String toolsVersion, String currentToolsVersion, RealmBusinessRules realm, PreviousVersionComparator previousVersionComparator, String dependencies, String csAnalysis, String versionRulesCheck, String copyrightYear, IWorkerContext context, Set r5Extensions, - List noNarratives, List noValidation, boolean noValidate, boolean noGenerate) { + List noNarratives, List noValidation, boolean noValidate, boolean noGenerate, DependentIGFinder dependentIgs) { super(); this.statedVersion = statedVersion; this.igVersion = igVersion; @@ -279,6 +282,7 @@ public ValidationPresenter(String statedVersion, String igVersion, IGKnowledgePr this.currentToolsVersion = currentToolsVersion; this.previousVersionComparator = previousVersionComparator; this.dependencies = dependencies; + this.dependentIgs = dependentIgs; this.csAnalysis = csAnalysis; this.versionRulesCheck = versionRulesCheck; this.copyrightYear = copyrightYear; @@ -389,7 +393,7 @@ else if (!vm.isSignpost()) { genQAText(title, files, path, filteredMessages, linkErrors); - String summary = "Errors: " + err + ", Warnings: " + warn + ", Info: " + info+", Broken Links = "+link; + String summary = "Errors: " + err + ", Warnings: " + warn + ", Info: " + info+", Broken Links: "+link; return path + "\r\n" + summary; } @@ -584,7 +588,7 @@ public static List filterMessages(List mes private final String headerTemplate = "\r\n"+ "\r\n"+ - "\r\n"+ + "\r\n"+ "\r\n"+ " $title$ : Validation Results\r\n"+ " \r\n"+ @@ -630,13 +634,14 @@ public static List filterMessages(List mes " Version Check:$versionRulesCheck$\r\n"+ " Supressed Messages:$suppressedmsgssummary$\r\n"+ " Dependency Checks:$dependencyCheck$\r\n"+ + " Dependent IGs:$dependentIgs$\r\n"+ " Publication Rules:Code = $igcode$. $ballotCheck$ $copyrightYearCheck$\r\n"+ " HTA Analysis:$csAnalysis$\r\n"+ " R5 Dependencies:$r5usage$\r\n"+ " Previous Version Comparison: $previousVersion$\r\n"+ "$noNarrative$"+ "$noValidation$"+ - " Summary: broken links = $links$, errors = $err$, warn = $warn$, info = $info$\r\n"+ + " Summary: errors = $err$, warn = $warn$, info = $info$, broken links = $links$\r\n"+ "\r\n"+ " \r\n"+ " \r\n"+ @@ -706,7 +711,7 @@ public static List filterMessages(List mes private final String footerTemplate = "\r\n"+ "\r\n"; - + // Text templates private final String headerTemplateText = "$title$ : Validation Results\r\n"+ @@ -729,8 +734,7 @@ public static List filterMessages(List mes private final String footerTemplateText = "\r\n"; - private String dependencies; - + private ST template(String t) { return new ST(t, '$', '$'); } @@ -758,10 +762,11 @@ private String genHeader(String title, int err, int warn, int info, int links, i t.add("versionRulesCheck", versionRulesCheck); t.add("realm", igrealm == null ? "n/a" : igrealm.toUpperCase()); t.add("dependencyCheck", dependencies); + t.add("dependentIgs", dependentIgs.getCountDesc()); t.add("csAnalysis", csAnalysis); t.add("r5usage", genR5()); t.add("otherFileName", allIssues ? "Errors Only" : "Full QA Report"); - t.add("otherFilePath", allIssues ? Utilities.getFileNameForName(Utilities.changeFileExt(path, ".min.html")) : Utilities.getFileNameForName(path)); + t.add("otherFilePath", allIssues ? "qa.min.html" : "qa.html"); t.add("previousVersion", previousVersionComparator.checkHtml()); t.add("noNarrative", genResourceList(noNarratives, "Narratives Suppressed")); t.add("noValidation", genResourceList(noValidation, "Validation Suppressed")); @@ -835,6 +840,7 @@ private String genHeaderTxt(String title, int err, int warn, int info) { t.add("igrealmerror", igRealmError); t.add("realm", igrealm == null ? "n/a" : igrealm.toUpperCase()); t.add("dependencyCheck", dependencies); + t.add("dependentIgs", dependentIgs.getCountDesc()); t.add("versionRulesCheck", versionRulesCheck); t.add("csAnalysis", csAnalysis); t.add("previousVersion", previousVersionComparator.checkHtml()); diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/spreadsheets/ObservationSummarySpreadsheetGenerator.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/spreadsheets/ObservationSummarySpreadsheetGenerator.java new file mode 100644 index 000000000..893803c1a --- /dev/null +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/spreadsheets/ObservationSummarySpreadsheetGenerator.java @@ -0,0 +1,109 @@ +package org.hl7.fhir.igtools.spreadsheets; + +import java.util.List; + +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.hl7.fhir.igtools.renderers.CrossViewRenderer.ObservationProfile; +import org.hl7.fhir.igtools.renderers.CrossViewRenderer.UsedType; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; +import org.hl7.fhir.r5.renderers.spreadsheets.SpreadsheetGenerator; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; + +public class ObservationSummarySpreadsheetGenerator extends SpreadsheetGenerator { + + + public ObservationSummarySpreadsheetGenerator(IWorkerContext context) { + super(context); + } + + public ObservationSummarySpreadsheetGenerator generate(List list) { + Sheet sheet = makeSheet("Observations"); + + Row headerRow = sheet.createRow(0); + addCell(headerRow, 0, "Profile", styles.get("body")); + addCell(headerRow, 1, "Name"); + addCell(headerRow, 2, "Category Code"); + addCell(headerRow, 3, "Category VS"); + addCell(headerRow, 4, "Code"); + addCell(headerRow, 5, "Code VS"); + addCell(headerRow, 6, "Time Types"); + addCell(headerRow, 7, "Value Types"); + addCell(headerRow, 8, "Data Absent Reason"); + addCell(headerRow, 9, "Body Site"); + addCell(headerRow, 10, "Method"); + + for (ObservationProfile op : list) { + Row row = sheet.createRow(sheet.getLastRowNum()+1); + addCell(row, 0, op.source.getId()); + addCell(row, 1, op.source.present()); + addCell(row, 2, renderCodes(op.category)); + addCell(row, 3, renderVS(op.catVS)); + addCell(row, 4, renderCodes(op.code)); + addCell(row, 5, renderVS(op.codeVS)); + addCell(row, 6, renderTypes(op.effectiveTypes)); + addCell(row, 7, renderTypes(op.types)); + addCell(row, 8, renderBoolean(op.dataAbsentReason)); + addCell(row, 9, renderCodes(op.bodySite)); + addCell(row, 10, renderCodes(op.method)); + + for (ObservationProfile op2 : op.components) { + row = sheet.createRow(sheet.getLastRowNum()+1); + addCell(row, 0, ""); + addCell(row, 1, op.source.present()); + addCell(row, 2, ""); + addCell(row, 3, ""); + addCell(row, 4, renderCodes(op2.code)); + addCell(row, 5, renderVS(op2.codeVS)); + addCell(row, 6, renderTypes(op2.effectiveTypes)); + addCell(row, 7, renderTypes(op2.types)); + addCell(row, 8, renderBoolean(op2.dataAbsentReason)); + addCell(row, 9, renderCodes(op2.bodySite)); + addCell(row, 10, renderCodes(op2.method)); + } + } + + return this; + } + + private String renderCodes(List codes) { + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(", "); + for (Coding t : codes) { + String sys = t.getUserString("desc"); + b.append(sys+"#"+t.getCode()); + } + return b.toString(); + } + + private String renderTypes(List types) { + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(", "); + for (UsedType t : types) { + if (t.ms) { + b.append(t.name+(char) 0x0135); + } else { + b.append(t.name); + } + } + return b.toString(); + } + + private String renderVS(ElementDefinitionBindingComponent binding) { + if (binding == null) { + return ""; + } else { + return binding.getValueSet()+" ("+binding.getStrength().toCode()+")"; + } + } + + private String renderBoolean(Boolean bool) { + if (bool == null) { + return "optional"; + } else if (bool) { + return "required"; + } else { + return "prohibited"; + } + } +} diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/TemplateManager.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/TemplateManager.java index c8fa99251..68cd83ada 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/TemplateManager.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/TemplateManager.java @@ -280,13 +280,13 @@ private NpmPackage loadPackage(String template, String rootFolder) throws FHIREx } private String zipUrl(String template) { - if (!template.startsWith("https://github.com")) { + if (!template.startsWith("https://github.")) { throw new FHIRException("Cannot refer to a template by URL unless referring to a github repository: "+template); } else if (Utilities.charCount(template, '/') == 4) { return Utilities.pathURL(template, "archive", "master.zip"); } else if (Utilities.charCount(template, '/') == 6) { String[] p = template.split("\\/"); - return Utilities.pathURL("https://github.com", p[3], p[4], "archive", p[6]+".zip"); + return Utilities.pathURL("https://"+p[2], p[3], p[4], "archive", p[6]+".zip"); } else { throw new FHIRException("Template syntax in URL referring to a github repository was not understood: "+template); } diff --git a/pom.xml b/pom.xml index 84028f52c..e24583ead 100644 --- a/pom.xml +++ b/pom.xml @@ -21,10 +21,10 @@ - 1.1.117-SNAPSHOT + 1.1.121-SNAPSHOT - 5.6.43 + 5.6.46 true