From a66ca2a1976f0a6d43045637b5a786b6d27a78e7 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 10 Oct 2022 17:05:12 +1100 Subject: [PATCH] Add JSON enhancements for CDS hooks logical model --- .../fhir/r5/conformance/ProfileUtilities.java | 57 +++++++++++++++---- .../hl7/fhir/r5/context/ContextUtilities.java | 4 +- .../fhir/r5/renderers/ValueSetRenderer.java | 5 +- .../hl7/fhir/r5/utils/ToolingExtensions.java | 5 +- .../xhtml/HierarchicalTableGenerator.java | 2 + 5 files changed, 57 insertions(+), 16 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 6a39db7f8..e04f455a8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -355,6 +355,7 @@ public class ProfileUtilities extends TranslatingUtilities { private boolean wantFixDifferentialFirstElementType; private Set masterSourceFileNames; private Map> childMapCache = new HashMap<>(); + private List keyRows = new ArrayList<>(); public ProfileUtilities(IWorkerContext context, List messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) { super(); @@ -1222,7 +1223,7 @@ public class ProfileUtilities extends TranslatingUtilities { String lid = tail(id); if (lid.contains("/")) { // the template comes from the snapshot of the base - generateIds(result.getElement(), url, srcSD.getType()); + generateIds(result.getElement(), url, srcSD.getType(), srcSD.getUrl()); String baseId = id.substring(0, id.length()-lid.length()) + lid.substring(0, lid.indexOf("/")); // this is wrong if there's more than one reslice (todo: one thing at a time) template = getById(result.getElement(), baseId); @@ -3976,12 +3977,23 @@ public class ProfileUtilities extends TranslatingUtilities { if (!max.isEmpty()) tracker.used = !max.getValue().equals("0"); + String hint = null; + if ("*".equals(max.getValue()) && 0 == min.getValue()) { + if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) { + String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY); + if ("present".equals(code)) { + hint = "This element is present as a JSON Array even when there are no items in the instance"; + } else { + hint = "This element may be present as a JSON Array even when there are no items in the instance"; + } + } + } Cell cell = gen.new Cell(null, null, null, null, null); row.getCells().add(cell); if (!min.isEmpty() || !max.isEmpty()) { - cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null))); - cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null))); - cell.addPiece(checkForNoChange(max, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null))); + cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), hint))); + cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", hint))); + cell.addPiece(checkForNoChange(max, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), hint))); } return cell; } @@ -4058,6 +4070,7 @@ public class ProfileUtilities extends TranslatingUtilities { } List profiles = new ArrayList(); profiles.add(profile); + keyRows.clear(); genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null, mustSupport, rc, anchorPrefix); try { @@ -4190,6 +4203,9 @@ public class ProfileUtilities extends TranslatingUtilities { } else if (!hasDef || element.getType().size() == 0) { if (root && context.getResourceNames().contains(profile.getType())) { row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE); + } else if (hasDef && element.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) { + row.setIcon("icon-object-box.png", HierarchicalTableGenerator.TEXT_ICON_OBJECT_BOX); + keyRows.add(element.getId()+"."+ToolingExtensions.readStringExtension(element, ToolingExtensions.EXT_JSON_PROP_KEY)); } else { row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); } @@ -4203,7 +4219,11 @@ public class ProfileUtilities extends TranslatingUtilities { } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@")) { row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE); } else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) { - row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); + if (keyRows.contains(element.getId())) { + row.setIcon("icon-key.png", HierarchicalTableGenerator.TEXT_ICON_KEY); + } else { + row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE); + } } else if (hasDef && element.getType().get(0).hasTarget()) { row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE); } else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) { @@ -4764,7 +4784,22 @@ public class ProfileUtilities extends TranslatingUtilities { if (root) { if (profile != null && profile.getAbstract()) { if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } - c.addPiece(gen.new Piece(null, "This is an abstract profile", null)); + c.addPiece(gen.new Piece(null, "This is an abstract "+(profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? "profile" : "type")+". ", null)); + + List children = new ArrayList<>(); + for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { + if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(profile.getUrl())) { + children.add(sd); + } + } + if (!children.isEmpty()) { + c.addPiece(gen.new Piece(null, "Child "+(profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? "profiles" : "types")+": ", null)); + boolean first = true; + for (StructureDefinition sd : children) { + if (first) first = false; else c.addPiece(gen.new Piece(null, ", ", null)); + c.addPiece(gen.new Piece(sd.getUserString("path"), sd.getType(), null)); + } + } } } if (definition.getPath().endsWith("url") && definition.hasFixed()) { @@ -5979,12 +6014,12 @@ public class ProfileUtilities extends TranslatingUtilities { if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) { if (!sd.hasDifferential()) sd.setDifferential(new StructureDefinitionDifferentialComponent()); - generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType()); + generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType(), sd.getUrl()); } if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) { if (!sd.hasSnapshot()) sd.setSnapshot(new StructureDefinitionSnapshotComponent()); - generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType()); + generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType(), sd.getUrl()); } } @@ -6029,7 +6064,7 @@ public class ProfileUtilities extends TranslatingUtilities { } - private void generateIds(List list, String name, String type) throws DefinitionException { + private void generateIds(List list, String name, String type, String url) throws DefinitionException { if (list.isEmpty()) return; @@ -6075,9 +6110,9 @@ public class ProfileUtilities extends TranslatingUtilities { if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) { String s = ed.getContentReference(); if (replacedIds.containsKey(s.substring(1))) { - ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+"#"+replacedIds.get(s.substring(1))); + ed.setContentReference(url+"#"+replacedIds.get(s.substring(1))); } else { - ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+s); + ed.setContentReference(url+s); } } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java index 486bca138..f1e3c0e37 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java @@ -148,8 +148,8 @@ public class ContextUtilities implements ProfileKnowledgeProvider { return null; } - CanonicalResource cr = context.fetchResource(CanonicalResource.class, url); - if (cr != null) { + if (context.hasResource(CanonicalResource.class, url)) { + CanonicalResource cr = context.fetchResource(CanonicalResource.class, url); return cr.getUserString("path"); } if (url.equals("http://loinc.org")) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java index b9e5632d4..3108cfe6c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java @@ -1115,13 +1115,14 @@ public class ValueSetRenderer extends TerminologyRenderer { // for performance reasons, we do all the fetching in one batch definitions = getConceptsForCodes(e, inc); + XhtmlNode t = li.table("none"); boolean hasComments = false; boolean hasDefinition = false; for (ConceptReferenceComponent c : inc.getConcept()) { hasComments = hasComments || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT); - ConceptDefinitionComponent cc = definitions.get(c.getCode()); + ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode()); hasDefinition = hasDefinition || ((cc != null && cc.hasDefinition()) || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION)); } if (hasComments || hasDefinition) @@ -1130,7 +1131,7 @@ public class ValueSetRenderer extends TerminologyRenderer { for (ConceptReferenceComponent c : inc.getConcept()) { XhtmlNode tr = t.tr(); XhtmlNode td = tr.td(); - ConceptDefinitionComponent cc = definitions.get(c.getCode()); + ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode()); addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td); td = tr.td(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java index 8e453d43c..2fae43699 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java @@ -133,7 +133,6 @@ public class ToolingExtensions { public static final String EXT_IGP_FORMAT = "http://hl7.org/fhir/StructureDefinition/igpublisher-res-format"; public static final String EXT_IGP_SOURCE = "http://hl7.org/fhir/StructureDefinition/igpublisher-res-source"; public static final String EXT_IGP_CONTAINED_RESOURCE_INFO = "http://hl7.org/fhir/tools/StructureDefinition/contained-resource-information"; - public static final String EXT_PRIVATE_BASE = "http://hl7.org/fhir/tools/"; public static final String EXT_BINARY_FORMAT = "http://hl7.org/fhir/StructureDefinition/implementationguide-resource-format"; public static final String EXT_IGP_RESOURCE_INFO = "http://hl7.org/fhir/tools/StructureDefinition/resource-information"; public static final String EXT_IGP_LOADVERSION = "http://hl7.org/fhir/StructureDefinition/igpublisher-loadversion"; @@ -208,7 +207,11 @@ public class ToolingExtensions { public static final String EXT_SD_DEPENDENCY = "http://hl7.org/fhir/StructureDefinition/structuredefinition-dependencies"; // in the tooling IG + public static final String EXT_PRIVATE_BASE = "http://hl7.org/fhir/tools/"; public static final String EXT_BINDING_ADDITIONAL = "http://hl7.org/fhir/tools/StructureDefinition/additional-binding"; + public static final String EXT_JSON_PROP_KEY = "http://hl7.org/fhir/tools/StructureDefinition/json-property-key"; + public static final String EXT_JSON_EMPTY = "http://hl7.org/fhir/tools/StructureDefinition/json-empty-behavior"; + // unregistered? - don't know what these are used for public static final String EXT_MAPPING_PREFIX = "http://hl7.org/fhir/tools/StructureDefinition/logical-mapping-prefix"; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/HierarchicalTableGenerator.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/HierarchicalTableGenerator.java index a599395e4..e0e5d0b83 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/HierarchicalTableGenerator.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/HierarchicalTableGenerator.java @@ -88,9 +88,11 @@ import org.hl7.fhir.utilities.Utilities; public class HierarchicalTableGenerator extends TranslatingUtilities { public static final String TEXT_ICON_REFERENCE = "Reference to another Resource"; public static final String TEXT_ICON_PRIMITIVE = "Primitive Data Type"; + public static final String TEXT_ICON_KEY = "JSON Key Value"; public static final String TEXT_ICON_DATATYPE = "Data Type"; public static final String TEXT_ICON_RESOURCE = "Resource"; public static final String TEXT_ICON_ELEMENT = "Element"; + public static final String TEXT_ICON_OBJECT_BOX = "Object"; public static final String TEXT_ICON_REUSE = "Reference to another Element"; public static final String TEXT_ICON_EXTENSION = "Extension"; public static final String TEXT_ICON_CHOICE = "Choice of Types";