diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java index f745853b9..a3b90c314 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java @@ -261,7 +261,7 @@ public class ProfilePathProcessor { private void processSimplePathDefault(ElementDefinition currentBase, String currentBasePath, List diffMatches, ProfilePathProcessorState cursors, MappingAssistant mapHelper) { // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct - if (!profileUtilities.unbounded(currentBase) && !profileUtilities.isSlicedToOneOnly(diffMatches.get(0))) + if (!profileUtilities.unbounded(currentBase) && !(profileUtilities.isSlicedToOneOnly(diffMatches.get(0)) || profileUtilities.isTypeSlicing(diffMatches.get(0)))) // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1 // (but you might do that in order to split up constraints by type) throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ATTEMPT_TO_A_SLICE_AN_ELEMENT_THAT_DOES_NOT_REPEAT__FROM__IN_, currentBase.getPath(), currentBase.getPath(), cursors.contextName, diffMatches.get(0).getId(), profileUtilities.sliceNames(diffMatches))); @@ -614,8 +614,10 @@ public class ProfilePathProcessor { } } if (firstTypeStructureDefinition != null) { - if (!profileUtilities.isMatchingType(firstTypeStructureDefinition, diffMatches.get(0).getType(), firstTypeProfile.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT))) { - throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE2, firstTypeStructureDefinition.getUrl(), diffMatches.get(0).getPath(), firstTypeStructureDefinition.getType(), firstTypeProfile.getValue(), diffMatches.get(0).getType().get(0).getWorkingCode())); + if (!profileUtilities.isGenerating(firstTypeStructureDefinition)) { // can't do this check while generating + if (!profileUtilities.isMatchingType(firstTypeStructureDefinition, diffMatches.get(0).getType(), firstTypeProfile.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT))) { + throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE2, firstTypeStructureDefinition.getUrl(), diffMatches.get(0).getPath(), firstTypeStructureDefinition.getType(), firstTypeProfile.getValue(), diffMatches.get(0).getType().get(0).getWorkingCode())); + } } if (profileUtilities.isGenerating(firstTypeStructureDefinition)) { // this is a special case, because we're only going to access the first element, and we can rely on the fact that it's already populated. @@ -638,8 +640,13 @@ public class ProfilePathProcessor { if (eid.equals(t.getId())) src = t; } - if (src == null) - throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT__IN_, eid, firstTypeProfile.getValue())); + if (src == null) { + if (profileUtilities.isGenerating(firstTypeStructureDefinition)) { + System.out.println("At this time the reference to "+eid+" cannot be handled - consult Grahame Grieve"); + } else if (Utilities.existsInList(currentBase.typeSummary(), "Extension", "Resource")) { + throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT__IN_, eid, firstTypeProfile.getValue())); + } + } } else { if (firstTypeStructureDefinition.getSnapshot().getElement().isEmpty()) { throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.SNAPSHOT_IS_EMPTY, firstTypeStructureDefinition.getVersionedUrl(), "Source for first element")); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index 89465ec1a..1f374e0a7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -843,6 +843,9 @@ public class ProfileUtilities { String msg = "No match found for "+e.getId()+" in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, "StructureDefinition.differential.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR)); } + } else { + ElementDefinition sed = (ElementDefinition) e.getUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT); + sed.setUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF, e); // note: this means diff/snapshot are cross-linked } i++; } @@ -1034,11 +1037,11 @@ public class ProfileUtilities { } private void checkTypeParameters(StructureDefinition base, StructureDefinition derived) { - String bt = ToolingExtensions.readStringExtension(base, ToolingExtensions.EXT_TYPE_PARAMETER); + String bt = ToolingExtensions.readStringSubExtension(base, ToolingExtensions.EXT_TYPE_PARAMETER, "type"); if (!derived.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) { throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_MISSING, base.getVersionedUrl(), bt, derived.getVersionedUrl())); } - String dt = ToolingExtensions.readStringExtension(derived, ToolingExtensions.EXT_TYPE_PARAMETER); + String dt = ToolingExtensions.readStringSubExtension(derived, ToolingExtensions.EXT_TYPE_PARAMETER, "type"); StructureDefinition bsd = context.fetchTypeDefinition(bt); StructureDefinition dsd = context.fetchTypeDefinition(dt); if (bsd == null) { @@ -1101,20 +1104,39 @@ public class ProfileUtilities { } } - private void copyInheritedExtensions(StructureDefinition base, StructureDefinition derived, String webUrl) { for (Extension ext : base.getExtension()) { - if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) && !derived.hasExtension(ext.getUrl())) { - Extension next = ext.copy(); - if (ext.hasValueMarkdownType()) { - MarkdownType md = ext.getValueMarkdownType(); - md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false)); + if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS)) { + String action = getExtensionAction(ext.getUrl()); + if (!"ignore".equals(action)) { + boolean exists = derived.hasExtension(ext.getUrl()); + if ("add".equals(action) || !exists) { + Extension next = ext.copy(); + if (next.hasValueMarkdownType()) { + MarkdownType md = next.getValueMarkdownType(); + md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false)); + } + derived.getExtension().add(next); + } else if ("overwrite".equals(action)) { + Extension oext = derived.getExtensionByUrl(ext.getUrl()); + Extension next = ext.copy(); + if (next.hasValueMarkdownType()) { + MarkdownType md = next.getValueMarkdownType(); + md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false)); + } + oext.setValue(next.getValue()); + } } - derived.getExtension().add(next); - } } - + } + + private String getExtensionAction(String url) { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); + if (sd != null && sd.hasExtension(ToolingExtensions.EXT_SNAPSHOT_BEHAVIOR)) { + return ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_SNAPSHOT_BEHAVIOR); + } + return "defer"; } private void addInheritedElementsForSpecialization(StructureDefinitionSnapshotComponent snapshot, ElementDefinition focus, String type, String path, String url, String weburl) { @@ -1490,17 +1512,18 @@ public class ProfileUtilities { } protected boolean isMatchingType(StructureDefinition sd, List types, String inner) { - while (sd != null) { + StructureDefinition tsd = sd; + while (tsd != null) { for (TypeRefComponent tr : types) { - if (sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && sd.getType().equals(tr.getCode())) { + if (tsd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && tsd.getType().equals(tr.getCode())) { return true; } - if (inner == null && sd.getUrl().equals(tr.getCode())) { + if (inner == null && tsd.getUrl().equals(tr.getCode())) { return true; } if (inner != null) { ElementDefinition ed = null; - for (ElementDefinition t : sd.getSnapshot().getElement()) { + for (ElementDefinition t : tsd.getSnapshot().getElement()) { if (inner.equals(t.getId())) { ed = t; } @@ -1510,7 +1533,7 @@ public class ProfileUtilities { } } } - sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); + tsd = context.fetchResource(StructureDefinition.class, tsd.getBaseDefinition(), tsd); } return false; } @@ -2261,6 +2284,13 @@ public class ProfileUtilities { return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1")); } + protected boolean isTypeSlicing(ElementDefinition e) { + return (e.hasSlicing() && e.getSlicing().getDiscriminator().size() == 1 && + e.getSlicing().getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE && + "$this".equals(e.getSlicing().getDiscriminatorFirstRep().getPath())); + } + + protected ElementDefinitionSlicingComponent makeExtensionSlicing() { ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); @@ -3109,6 +3139,10 @@ public class ProfileUtilities { if (sd != null && sd.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) { return true; } + if (sd != null && sd.hasExtension(ToolingExtensions.EXT_TYPE_CHARACTERISTICS) && + "can-bind".equals(ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_TYPE_CHARACTERISTICS))) { + return true; + } } return false; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java index 7925661cc..e86f381a9 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java @@ -292,7 +292,7 @@ public class DataRenderer extends Renderer implements CodeResolver { return b.toString(); } - private String lookupCode(String system, String version, String code) { + public String lookupCode(String system, String version, String code) { if (JurisdictionUtilities.isJurisdiction(system)) { return JurisdictionUtilities.displayJurisdiction(system+"#"+code); } @@ -924,7 +924,7 @@ public class DataRenderer extends Renderer implements CodeResolver { } /** - * this is overriden in ResourceRenderer where a better rendering is performed + * this is overridden in ResourceRenderer where a better rendering is performed * @param status * @param x * @param ref @@ -1136,7 +1136,7 @@ public class DataRenderer extends Renderer implements CodeResolver { return s; } - private String displayCodeSource(String system, String version) { + public String displayCodeSource(String system, String version) { String s = displaySystem(system); if (version != null) { s = s + "["+describeVersion(version)+"]"; @@ -1212,7 +1212,7 @@ public class DataRenderer extends Renderer implements CodeResolver { } } - private String getLinkForSystem(String system, String version) { + public String getLinkForSystem(String system, String version) { if ("http://snomed.info/sct".equals(system)) { return "https://browser.ihtsdotools.org/"; } else if ("http://loinc.org".equals(system)) { @@ -1234,7 +1234,7 @@ public class DataRenderer extends Renderer implements CodeResolver { } } - protected String getLinkForCode(String system, String version, String code) { + public String getLinkForCode(String system, String version, String code) { if ("http://snomed.info/sct".equals(system)) { return SnomedUtilities.getSctLink(version, code, context.getContext().getExpansionParameters()); } else if ("http://loinc.org".equals(system)) { @@ -2097,36 +2097,39 @@ public class DataRenderer extends Renderer implements CodeResolver { if (rep.has("count")) b.append(context.formatPhrase(RenderingContext.DATA_REND_COUNT, rep.primitiveValue("count")) + " " + " times"); if (rep.has("duration")) - b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.primitiveValue("duration")+displayTimeUnits(rep.primitiveValue("periodUnit"))) + " "); + b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.primitiveValue("duration")+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("duration")))) + " "); - if (rep.has("when")) { - String st = ""; - if (rep.has("offset")) { - st = rep.primitiveValue("offset")+"min "; - } - b.append(st); - for (ResourceWrapper wh : rep.children("when")) { - b.append(displayEventCode(wh.primitiveValue())); - } - } else { - String st = ""; - if (!rep.has("frequency") || (!rep.has("frequencyMax") && rep.primitiveValue("frequency").equals("1"))) { - st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); - } else { - st = rep.primitiveValue("frequency"); - if (rep.has("frequencyMax")) - st = st + "-"+rep.primitiveValue("frequencyMax"); - } - if (rep.has("period")) { - st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+" "+rep.primitiveValue("period"); - if (rep.has("periodMax")) - st = st + "-"+rep.primitiveValue("periodMax"); - st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit")); - } - b.append(st); + String st = ""; + if (rep.has("offset")) { + st = rep.primitiveValue("offset")+"min "; } - if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("end")) - b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.child("boundsPeriod").child("end"))) + " "); + if (!Utilities.noString(st)) { + b.append(st); + } + for (ResourceWrapper wh : rep.children("when")) { + b.append(displayEventCode(wh.primitiveValue())); + } + st = ""; + if (!rep.has("frequency") || (!rep.has("frequencyMax") && rep.primitiveValue("frequency").equals("1"))) { + st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); + } else { + st = rep.primitiveValue("frequency"); + if (rep.has("frequencyMax")) + st = st + "-"+rep.primitiveValue("frequencyMax"); + } + if (rep.has("period")) { + st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+" "+rep.primitiveValue("period"); + if (rep.has("periodMax")) { + st = st + "-"+rep.primitiveValue("periodMax"); + } + st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("period"))); + } + if (!Utilities.noString(st)) { + b.append(st); + } + if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("end")) { + b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.child("boundsPeriod").child("end"))) + " "); + } } return b.toString(); } @@ -2141,7 +2144,9 @@ public class DataRenderer extends Renderer implements CodeResolver { } private String displayEventCode(String when) { - switch (when) { + if (when == null) + return "??"; + switch (when.toLowerCase()) { case "c": return (context.formatPhrase(RenderingContext.DATA_REND_MEALS)); case "cd": return (context.formatPhrase(RenderingContext.DATA_REND_ATLUNCH)); case "cm": return (context.formatPhrase(RenderingContext.DATA_REND_ATBKFST)); @@ -2156,22 +2161,36 @@ public class DataRenderer extends Renderer implements CodeResolver { case "pcm": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRBKFST)); case "pcv": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRDINR)); case "wake": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRWKNG)); - default: return "?ngen-6?"; + case "morn": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING)); + case "morn.early": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_EARLY)); + case "morn.late": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_LATE)); + case "noon": return (context.formatPhrase(RenderingContext.DATA_REND_NOON)); + case "aft": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON)); + case "aft.early": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_EARLY)); + case "aft.late": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_LATE)); + case "eve": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING)); + case "eve.early": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_EARLY)); + case "eve.late": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_LATE)); + case "night": return (context.formatPhrase(RenderingContext.DATA_REND_NIGHT)); + case "phs": return (context.formatPhrase(RenderingContext.DATA_REND_AFTER_SLEEP)); + case "imd": return (context.formatPhrase(RenderingContext.DATA_REND_IMMEDIATE)); + + default: return "?"+when+"?"; } } - private String displayTimeUnits(String units) { + private String displayTimeUnits(String units, boolean singular) { if (units == null) - return "?ngen-7?"; + return "??"; switch (units) { - case "a": return "years"; - case "d": return "days"; - case "h": return "hours"; - case "min": return "minutes"; - case "mo": return "months"; - case "s": return "seconds"; - case "wk": return "weeks"; - default: return "?ngen-8?"; + case "a": return singular ? "year" : "years"; + case "d": return singular ? "day" : "days"; + case "h": return singular ? "hour" : "hours"; + case "min": return singular ? "minute" : "minutes"; + case "mo": return singular ? "month" : "months"; + case "s": return singular ? "second" : "seconds"; + case "wk": return singular ? "week" : "weeks"; + default: return "?"+units+"?"; } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index 39c745b2f..05df6d766 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -65,6 +65,18 @@ import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingCompo import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.r5.model.UriType; import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.renderers.utils.ElementTable; +import org.hl7.fhir.r5.renderers.utils.ElementTable.ElementTableGrouping; +import org.hl7.fhir.r5.renderers.utils.ElementTable.ElementTableGroupingEngine; +import org.hl7.fhir.r5.renderers.utils.ElementTable.ElementTableGroupingState; +import org.hl7.fhir.r5.renderers.utils.ElementTable.HintDrivenGroupingEngine; +import org.hl7.fhir.r5.renderers.utils.ElementTable.JsonDrivenGroupingEngine; +import org.hl7.fhir.r5.renderers.utils.ElementTable.TableElement; +import org.hl7.fhir.r5.renderers.utils.ElementTable.TableElementConstraint; +import org.hl7.fhir.r5.renderers.utils.ElementTable.TableElementConstraintType; +import org.hl7.fhir.r5.renderers.utils.ElementTable.TableElementDefinition; +import org.hl7.fhir.r5.renderers.utils.ElementTable.TableElementDefinitionType; +import org.hl7.fhir.r5.renderers.utils.ElementTable.TableGroup; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.FixedValueFormat; import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; @@ -83,6 +95,9 @@ import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.i18n.I18nConstants; +import org.hl7.fhir.utilities.i18n.RenderingI18nContext; +import org.hl7.fhir.utilities.json.JsonException; +import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; @@ -91,7 +106,7 @@ import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMo import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; -import org.hl7.fhir.utilities.xhtml.XhtmlParser; +import org.hl7.fhir.utilities.xhtml.XhtmlParser; public class StructureDefinitionRenderer extends ResourceRenderer { @@ -372,7 +387,8 @@ public class StructureDefinitionRenderer extends ResourceRenderer { private final boolean ADD_REFERENCE_TO_TABLE = true; private boolean useTableForFixedValues = true; - private String corePath; + private String corePath; + private JsonObject resourceGroupings; public static class UnusedTracker { private boolean used; @@ -703,6 +719,24 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return model; } + + public TableModel initElementTable(HierarchicalTableGenerator gen, String prefix, boolean alternating, String id, boolean isActive, TableGenerationMode mode) throws IOException { + TableModel model = gen.new TableModel(id, isActive); + + model.setAlternating(alternating); + if (mode == TableGenerationMode.XML) { + model.setDocoImg(HierarchicalTableGenerator.help16AsData()); + } else { + model.setDocoImg(Utilities.pathURL(prefix, "help16.png")); + } + model.setDocoRef(Utilities.pathURL("https://build.fhir.org/ig/FHIR/ig-guidance", "readingIgs.html#table-views")); + // these need to be defined, but they're never going to be shown + model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingI18nContext.GENERAL_NAME), context.formatPhrase(RenderingI18nContext.GENERAL_LOGICAL_NAME), null, 0)); + model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingI18nContext.GENERAL_DESC_CONST), context.formatPhrase(RenderingI18nContext.SD_HEAD_DESC_DESC), null, 0)); + model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingI18nContext.GENERAL_TYPE), context.formatPhrase(RenderingI18nContext.SD_GRID_HEAD_TYPE_DESC), null, 0)); + return model; + } + private Row genElement(RenderingStatus status, String defPath, HierarchicalTableGenerator gen, List rows, ElementDefinition element, List all, List profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants, Row slicingRow, boolean mustSupport, RenderingContext rc, String anchorPrefix, Resource srcSD, List columns, ResourceWrapper res) throws IOException, FHIRException { Row originalRow = slicingRow; @@ -4931,4 +4965,325 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return "" + coding.getCode() + "" + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")"); } + public XhtmlNode buildElementTable(RenderingStatus status, String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, + boolean logicalModel, boolean allInvariants, Set outputTracker, boolean mustSupport, RenderingContext rc, String anchorPrefix, ResourceWrapper res) throws IOException { + // in order to build this, we need to know the base type of the profile + ElementTableGroupingEngine groupings = getElementTableGroupings(profile.getType()); + if (groupings == null) { + XhtmlNode node = new XhtmlNode(NodeType.Element, "div"); + node.para().tx("This view is not supported for this profile because it is of an unsupported type"); + return node; + } else if (profile.getSnapshot().getElement().isEmpty()) { + XhtmlNode node = new XhtmlNode(NodeType.Element, "div"); + node.para().tx("This view is not supported for this profile because it doesn't have a snapshot"); + return node; + } else { + List groups = new ArrayList(); + List children = context.getProfileUtilities().getChildList(profile, profile.getSnapshot().getElementFirstRep()); + buildElementTableRows(profile, groupings, groups, children); + + HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, imageFolder, inlineGraphics, true, defFile, rc.getUniqueLocalPrefix()); + gen.setTreelines(false); + TableModel model = initElementTable(gen, corePath, true, profile.getId()+"e", true, TableGenerationMode.XHTML); + new ElementTable(context, groups, this).build(gen, model); + + try { + return gen.generate(model, imagePath, 0, outputTracker); + } catch (org.hl7.fhir.exceptions.FHIRException e) { + throw new FHIRException(context.getWorker().formatMessage(I18nConstants.ERROR_GENERATING_TABLE_FOR_PROFILE__, profile.getUrl(), e.getMessage()), e); + } + } + } + + private void buildElementTableRows(StructureDefinition profile, ElementTableGroupingEngine groupings, List groups, List elements) { + for (ElementDefinition ed : elements) { + ElementTableGroupingState state = groupings.groupState(ed); + ElementTable.TableGroup group = getOrMakeGroup(groupings, groups, ed); + if (group != null) { + boolean isLeaf = isLeaf(ed); + if (isLeaf) { + if (hasAnyDiff(profile, ed) && !ed.isProhibited()) { + group.getElements().add(makeElement(profile, null, ed, true)); + } + } else if (state == ElementTableGroupingState.DEFINES_GROUP) { + List children = context.getProfileUtilities().getChildList(profile, ed); + buildElementTableRows(profile, group, children, ed); + } else if (hasAnyDiff(profile, ed) && !ed.isProhibited()) { + TableElement e = makeElement(profile, null, ed, false); + group.getElements().add(e); + List children = context.getProfileUtilities().getChildList(profile, ed); + buildElementTableRows(profile, e, children); + } + } else if (!ed.isProhibited()) { + // walk the children without creating anything. Will only do anything in a logical model + List children = context.getProfileUtilities().getChildList(profile, ed); + buildElementTableRows(profile, groupings, groups, children); + } + } + } + + + private void buildElementTableRows(StructureDefinition profile, ElementTable.TableGroup group, List elements, ElementDefinition parent) { + for (ElementDefinition ed : elements) { + if (hasAnyDiff(profile, ed) && !ed.isProhibited()) { + boolean isLeaf = isLeaf(ed); + if (isLeaf) { + if (useParent(ed) ) { + group.getElements().add(makeElement(profile, parent, ed, true)); + } else { + group.getElements().add(makeElement(profile, null, ed, true)); + } + } else { + List children = context.getProfileUtilities().getChildList(profile, ed); + buildElementTableRows(profile, group, children, ed); + } + } + } + } + + + private boolean useParent(ElementDefinition ed) { + for (TypeRefComponent t : ed.getType()) { + StructureDefinition sd = context.getContext().fetchTypeDefinition(t.getWorkingCode()); + if (sd != null && sd.hasExtension(ToolingExtensions.EXT_PROFILE_VIEW_HINT)) { + for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_VIEW_HINT)) { + if (ext.hasValue()) { + String v = ext.getValue().primitiveValue(); + if ("element-view-defns-parent".equals(v)) { + return true; + } + } + } + } + } + return true; + } + + private void buildElementTableRows(StructureDefinition profile, TableElement parent, List elements) { + for (ElementDefinition ed : elements) { + if (hasAnyDiff(profile, ed) && !ed.isProhibited()) { + boolean isLeaf = isLeaf(ed); + if (isLeaf) { + parent.getChildElements().add(makeElement(profile, null, ed, true)); + } else { + TableElement e = makeElement(profile, null, ed, false); + parent.getChildElements().add(e); + List children = context.getProfileUtilities().getChildList(profile, ed); + buildElementTableRows(profile, e, children); + } + } + } + } + + private boolean isLeaf(ElementDefinition element) { + for (TypeRefComponent t : element.getType()) { + if (context.getContextUtilities().isDatatype(t.getWorkingCode()) && !Utilities.existsInList(t.getWorkingCode(), "Element", "BackboneElement")) { + return true; + } + StructureDefinition sd = context.getContext().fetchTypeDefinition(t.getWorkingCode()); + if (sd != null && sd.hasExtension(ToolingExtensions.EXT_PROFILE_VIEW_HINT)) { + for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_VIEW_HINT)) { + if (ext.hasValue()) { + String v = ext.getValue().primitiveValue(); + if ("element-view-as-leaf".equals(v)) { + return true; + } + } + } + } + } + return false; + } + + private boolean hasAnyDiff(StructureDefinition profile, ElementDefinition e) { + if (e.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF)) { + ElementDefinition diff = (ElementDefinition) e.getUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF); + return hasDefinitionalMaterial(diff); + } + for (ElementDefinition child : context.getProfileUtilities().getChildList(profile, e.getPath(), e.getId(), false, false)) { + if (hasAnyDiff(profile, child)) { + return true; + } + } + return false; + } + + private boolean hasDefinitionalMaterial(ElementDefinition diff) { + return diff.hasShort() || diff.hasDefinition() || diff.hasComment() || diff.hasRequirements() || + diff.hasBinding() || diff.hasFixed() || diff.hasPattern() || diff.hasMin() || diff.hasMax() || + diff.hasMinValue() || diff.hasMaxValue() || diff.hasConstraint() || diff.hasType() || diff.hasMaxLength(); + } + + private ElementTableGroupingEngine getElementTableGroupings(String type) throws JsonException, IOException { + StructureDefinition sd = context.getContext().fetchTypeDefinition(type); + if (sd == null) { + return null; + } + if (sd.hasExtension(ToolingExtensions.EXT_PROFILE_VIEW_HINT) && "element-view-ready".equals(ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_PROFILE_VIEW_HINT))) { + return new HintDrivenGroupingEngine(context.getProfileUtilities().getChildList(sd, sd.getSnapshot().getElementFirstRep())); + } + if (resourceGroupings == null) { + byte[] cnt = context.getContext().getBinaryForKey("resource-groupings.json"); + if (cnt != null) { + resourceGroupings = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(cnt, true); + } + } + if (resourceGroupings != null && resourceGroupings.has(type)) { + return new JsonDrivenGroupingEngine(resourceGroupings.getJsonArray(type)); + } else { + return null; + } + } + + private TableGroup getOrMakeGroup(ElementTableGroupingEngine groupings, List groups, ElementDefinition element) { + ElementTableGrouping grouping = groupings.getGroup(element); + if (grouping == null) { + return null; + } + for (TableGroup g : groups) { + if (g.getDefinition().getKey() == grouping.getKey()) { + return g; + } + } + TableGroup g = new TableGroup(groups.size()+1, grouping); + groups.add(g); + return g; + } + + private TableElement makeElement(StructureDefinition profile, ElementDefinition parentDefn, ElementDefinition snapDefn, boolean lookAtChildren) { + ElementDefinition working = parentDefn != null ? parentDefn : snapDefn; + ElementDefinition diffDefn = (ElementDefinition) snapDefn.getUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF); + ElementDefinition diffParentDefn = parentDefn == null ? null : (ElementDefinition) parentDefn.getUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF); + ElementDefinition workingDiff = diffParentDefn != null ? diffParentDefn : diffDefn; + + + TableElement e = new TableElement(working.getPath(), working.getShort(), Integer.toString(working.getMin()), working.getMax()); + + if (snapDefn.getType().size() > 1) { + List list = new ArrayList(); + for (TypeRefComponent tr : snapDefn.getType()) { + StructureDefinition sd = context.getContext().fetchTypeDefinition(tr.getWorkingCode()); + list.add(sd == null ? tr.getWorkingCode() : sd.present()); + } + e.setType("Choice", null, "Choice of "+CommaSeparatedStringBuilder.join(",", list), "icon_choice.gif"); + e.getConstraints().add(TableElementConstraint.makeTypes(TableElementConstraintType.CHOICE, null, snapDefn.getType())); + } else { + StructureDefinition sd = context.getContext().fetchTypeDefinition(snapDefn.getTypeFirstRep().getWorkingCode()); + if (sd == null) { + e.setType(snapDefn.getTypeFirstRep().getWorkingCode(), null, "Unknown Type", "icon_element.gif"); + } else if (Utilities.existsInList(sd.getType(), "Element", "BackboneElement")) { + e.setType("Group", sd.getWebPath(), sd.present(), chooseIcon(profile, snapDefn, snapDefn.getTypeFirstRep())); + } else { + e.setType(sd.present(), sd.getWebPath(), sd.present(), chooseIcon(profile, snapDefn, snapDefn.getTypeFirstRep())); + } + if (snapDefn.getTypeFirstRep().hasProfile()) { + e.getConstraints().add(TableElementConstraint.makeList(TableElementConstraintType.PROFILE, null, snapDefn.getTypeFirstRep().getProfile())); + } + if (snapDefn.getTypeFirstRep().hasTargetProfile()) { + e.getConstraints().add(TableElementConstraint.makeList(TableElementConstraintType.TARGET, null, snapDefn.getTypeFirstRep().getTargetProfile())); + } + } + + if (working.hasDefinition()) { + e.getDefinitions().add(new TableElementDefinition(TableElementDefinitionType.DEFINITION, working.getDefinition())); + } else if (snapDefn.hasDefinition()) { + e.getDefinitions().add(new TableElementDefinition(TableElementDefinitionType.DEFINITION, snapDefn.getDefinition())); + } + if (workingDiff != null && workingDiff.hasComment()) { + e.getDefinitions().add(new TableElementDefinition(TableElementDefinitionType.COMMENT, workingDiff.getComment())); + } else if (diffDefn != null && diffDefn.hasComment()) { + e.getDefinitions().add(new TableElementDefinition(TableElementDefinitionType.COMMENT, diffDefn.getComment())); + } + if (workingDiff != null && workingDiff.hasRequirements()) { + e.getDefinitions().add(new TableElementDefinition(TableElementDefinitionType.REQUIREMENTS, workingDiff.getRequirements())); + } else if (diffDefn != null && diffDefn.hasRequirements()) { + e.getDefinitions().add(new TableElementDefinition(TableElementDefinitionType.REQUIREMENTS, diffDefn.getRequirements())); + } + + checkValueDomainConstraints(snapDefn, diffDefn, null, e, false); + if (lookAtChildren) { + List children = context.getProfileUtilities().getChildList(profile, snapDefn); + for (ElementDefinition child : children) { + checkValueDomainConstraints(child, (ElementDefinition) child.getUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF), child.getName(), e, true); + } + } + return e; + } + + public void checkValueDomainConstraints(ElementDefinition defn, ElementDefinition diffDefn, String path, TableElement e, boolean cardinality) { + if (cardinality) { + if (defn.getBase().getMin() != defn.getMin() || !defn.getBase().getMax().equals(defn.getMax())) { + e.getConstraints().add(TableElementConstraint.makeRange(TableElementConstraintType.CARDINALITY, path, defn.getMinElement(), defn.getMaxElement())); + } + } + // ok, now we collect constraints on the value domain, which maybe in sub-elements, though at some point we give up + if (defn.hasFixed()) { + e.getConstraints().add(TableElementConstraint.makeValue(TableElementConstraintType.FIXED, path, defn.getFixed())); + } + if (defn.hasPattern()) { + e.getConstraints().add(TableElementConstraint.makeValue(TableElementConstraintType.PATTERN, path, defn.getPattern())); + } + if (defn.hasMinValue() || defn.hasMaxValue()) { + e.getConstraints().add(TableElementConstraint.makeRange(TableElementConstraintType.RANGE, path, defn.getMinValue(), defn.getMaxValue())); + } + if (defn.hasMaxLength()) { + e.getConstraints().add(TableElementConstraint.makeValue(TableElementConstraintType.MAXLENGTH, path, defn.getMaxLengthElement())); + } + if (defn.hasBinding() && defn.getBinding().hasValueSet() && (!cardinality || (diffDefn != null && diffDefn.hasBinding())) && !defn.hasFixed()) { + e.getConstraints().add(TableElementConstraint.makeBinding(TableElementConstraintType.BINDING, path, defn.getBinding().getStrength(), defn.getBinding().getValueSet())); + } + } + + private String chooseIcon(StructureDefinition profile, ElementDefinition element, TypeRefComponent tr) { + + if (tail(element.getPath()).equals("extension") && isExtension(element)) { + if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) + return "icon_extension_complex.png"; + else + return "icon_extension_simple.png"; + } else if (tail(element.getPath()).equals("modifierExtension")) { + if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue())) + return "icon_modifier_extension_complex.png"; + else + return "icon_modifier_extension_simple.png"; + } else if (element.getType().size() == 0) { + if (profile != null && context.getWorker().getResourceNames().contains(profile.getType())) { + return "icon_resource.png"; + } else if (element.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) { + return "icon-object-box.png"; + } else { + return "icon_element.gif"; + } + } else if (element.getType().size() > 1) { + if (allAreReference(element.getType())) { + return "icon_reference.png"; + } else if (element.hasExtension(ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) { + return "icon_choice.gif"; + } else { + return "icon_choice.gif"; + } + } else if (element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@")) { + return "icon_reuse.png"; + } else if (context.getContext().isPrimitiveType(element.getType().get(0).getWorkingCode())) { + if (keyRows.contains(element.getId())) { + return "icon-key.png"; + } else { + return "icon_primitive.png"; + } + } else if (element.getType().get(0).hasTarget()) { + return "icon_reference.png"; + } else if (Utilities.existsInList(element.getType().get(0).getWorkingCode(), "Element", "BackboneElement")) { + return "icon_group.png"; + } else if (Utilities.existsInList(element.getType().get(0).getWorkingCode(), "Base", "Element", "BackboneElement")) { + return "icon_element.gif"; + } else if (context.getContext().isDataType(element.getType().get(0).getWorkingCode())) { + return "icon_datatype.gif"; + } else if (element.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) { + return "icon-object-box.png"; + } else { + return "icon_resource.png"; + } + + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementTable.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementTable.java new file mode 100644 index 000000000..1a05e4d31 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementTable.java @@ -0,0 +1,854 @@ +package org.hl7.fhir.r5.renderers.utils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.CanonicalType; +import org.hl7.fhir.r5.model.CodeableConcept; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.DataType; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.model.Enumerations.BindingStrength; +import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.Quantity; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.r5.renderers.DataRenderer; +import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus; +import org.hl7.fhir.r5.renderers.utils.ElementTable.HintDrivenGroupingEngine; +import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; +import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.json.model.JsonArray; +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; +import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; +import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; +import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; + + +public class ElementTable { + + public static class ElementTableGrouping { + + private long key; + private String name; + private int priority; + + public ElementTableGrouping(long key, String name, int priority) { + this.key = key; + this.name = name; + this.priority = priority; + } + + public String getName() { + return name; + } + + public int getPriority() { + return priority; + } + + public long getKey() { + return key; + } + + } + public enum ElementTableGroupingState { + UNKNOWN, DEFINES_GROUP, IN_GROUP + } + public static abstract class ElementTableGroupingEngine { + + public abstract ElementTableGroupingState groupState(ElementDefinition ed); + public abstract ElementTableGrouping getGroup(ElementDefinition ed); + + + protected boolean nameMatches(String name, List strings) { + for (String s : strings) { + if (nameMatches(name, s)) { + return true; + } + } + return false; + } + + public boolean nameMatches(String name, String test) { + if (test.equals(name)) { + return true; + } + if (test.endsWith("[x]") && name.startsWith(test.substring(0, test.length()-3))) { + return true; + } + return false; + } + } + + public static class JsonDrivenGroupingEngine extends ElementTableGroupingEngine { + + private JsonArray groups; + + public JsonDrivenGroupingEngine(JsonArray groups) { + super(); + this.groups = groups; + } + + public ElementTableGroupingState groupState(ElementDefinition ed) { + String name = ed.getName(); + for (JsonObject o : groups.asJsonObjects() ) { + if (nameMatches(name, o.getStrings("elements"))) { + return ElementTableGroupingState.IN_GROUP; + } + } + for (JsonObject o : groups.asJsonObjects() ) { + if (o.asBoolean("all")) { + return ElementTableGroupingState.IN_GROUP; + } + } + return ElementTableGroupingState.UNKNOWN; + } + + public ElementTableGrouping getGroup(ElementDefinition ed) { + String name = ed.getName(); + int c = 0; + for (JsonObject o : groups.asJsonObjects() ) { + c++; + if (nameMatches(name, o.getStrings("elements"))) { + return new ElementTableGrouping(c, o.asString("name"), groups.size() - c); + } + } + c = 0; + for (JsonObject o : groups.asJsonObjects() ) { + c++; + if (o.asBoolean("all")) { + return new ElementTableGrouping(c, o.asString("name"), groups.size() - c); + } + } + return null; + } + } + + public static class HintDrivenGroupingEngine extends ElementTableGroupingEngine { + + private List list; + + public HintDrivenGroupingEngine(List list) { + super(); + this.list = list; + } + + public ElementTableGroupingState groupState(ElementDefinition ed) { + if (ed.hasExtension(ToolingExtensions.EXT_PROFILE_VIEW_HINT)) { + List exl = ed.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_VIEW_HINT); + for (Extension ex : exl) { + if ("element-view-group".equals(ex.getExtensionString("name")) ) { + return ElementTableGroupingState.DEFINES_GROUP; + } + } + } + return ElementTableGroupingState.UNKNOWN; + } + + public ElementTableGrouping getGroup(ElementDefinition ed) { + if (ed.hasExtension(ToolingExtensions.EXT_PROFILE_VIEW_HINT)) { + String n = null; + int order = 0; + List exl = ed.getExtensionsByUrl(ToolingExtensions.EXT_PROFILE_VIEW_HINT); + for (Extension ex : exl) { + if ("element-view-group".equals(ex.getExtensionString("name")) ) { + n = ex.getExtensionString("value"); + } + if ("element-view-order".equals(ex.getExtensionString("name")) ) { + order = ToolingExtensions.readIntegerExtension(ex, "value", 0); + } + } + if (n != null) { + return new ElementTableGrouping(ed.getName().hashCode(), n, order); + } + } + return null; + } + } + + + public static enum TableElementDefinitionType { + DEFINITION, COMMENT, REQUIREMENTS; + } + + public static class TableElementDefinition { + private TableElementDefinitionType type; + private String markdown; + public TableElementDefinition(TableElementDefinitionType type, String markdown) { + super(); + this.type = type; + this.markdown = markdown; + } + public TableElementDefinitionType getType() { + return type; + } + public String getMarkdown() { + return markdown; + } + + } + + public enum TableElementConstraintType { + CHOICE, PROFILE, TARGET, BINDING, RANGE, FIXED, PATTERN, MAXLENGTH, CARDINALITY; + } + + public static class TableElementConstraint { + private TableElementConstraintType type; + private DataType value; + private DataType value2; + private String path; + private BindingStrength strength; + private String valueSet; + private List types; + private List list; + + public static TableElementConstraint makeValue(TableElementConstraintType type, String path, DataType value) { + TableElementConstraint self = new TableElementConstraint(); + self.type = type; + self.path = path; + self.value = value; + return self; + } + + public static TableElementConstraint makeRange(TableElementConstraintType type, String path, DataType value, DataType value2) { + TableElementConstraint self = new TableElementConstraint(); + self.type = type; + self.path = path; + self.value = value; + self.value2 = value2; + return self; + } + + public static TableElementConstraint makeBinding(TableElementConstraintType type, String path, BindingStrength strength, String valueSet) { + TableElementConstraint self = new TableElementConstraint(); + self.type = type; + self.path = path; + self.strength = strength; + self.valueSet = valueSet; + return self; + } + + public static TableElementConstraint makeTypes(TableElementConstraintType type, String path, List types) { + TableElementConstraint self = new TableElementConstraint(); + self.type = type; + self.path = path; + self.types = types; + return self; + } + + public static TableElementConstraint makeList(TableElementConstraintType type, String path, List list) { + TableElementConstraint self = new TableElementConstraint(); + self.type = type; + self.path = path; + self.list = list; + return self; + } + +// private BindingStrength strength; +// private ValueSet vs; +// private List profiles; +// private List targetProfiles; +// private DataType fixed; +// private DataType pattern; +// private DataType minValue; +// private DataType maxValue; + } + + + public class ConstraintsSorter implements Comparator { + + @Override + public int compare(TableElementConstraint o1, TableElementConstraint o2) { + int r = StringUtils.compare(o1.path, o2.path); + return r == 0 ? o1.type.compareTo(o2.type) : r; + } + } + + public static class TableElementInvariant { + private String level; + private String human; + private String fhirPath; + private String other; + private String otherFormat; + } + + public static class TableElement { + // column 1 + private String path; + private String name; + private String min; + private String max; + private String typeName; + private String typeIcon; + private String typeLink; + private String typeHint; + + // column 2 + private List definitions = new ArrayList<>(); + + // column 3 + private List constraints = new ArrayList<>(); + + private List invariants = new ArrayList<>(); + + private List childElements = new ArrayList(); + + public TableElement(String path, String name, String min, String max) { + super(); + this.path = path; + this.name = name; + this.min = min; + this.max = max; + } + + public String getPath() { + return path; + } + + public String getName() { + return name; + } + + public String getMin() { + return min; + } + + public String getMax() { + return max; + } + public String getTypeName() { + return typeName; + } + public String getTypeIcon() { + return typeIcon; + } + public String getTypeLink() { + return typeLink; + } + public String getTypeHint() { + return typeHint; + } + + public List getDefinitions() { + return definitions; + } + + public List getConstraints() { + return constraints; + } + + public List getInvariants() { + return invariants; + } + + public List getChildElements() { + return childElements; + } + + public TableElement setPath(String path) { + this.path = path; + return this; + } + + public TableElement setName(String name) { + this.name = name; + return this; + } + + public TableElement setMin(String min) { + this.min = min; + return this; + } + + public TableElement setMax(String max) { + this.max = max; + return this; + } + + public void setType(String name, String link, String hint, String icon) { + this.typeName = name; + this.typeIcon = icon; + this.typeLink = link; + this.typeHint = hint; + } + + } + + public static class TableGroup { + private String name; + private String documentation; + private boolean buildIfEmpty; + private String emptyNote; + private List elements = new ArrayList(); + private int priorty; + private int counter; + private ElementTableGrouping definition; + + public TableGroup(int counter, ElementTableGrouping definition) { + super(); + this.counter = counter; + this.definition = definition; + name = definition.getName(); + priorty = definition.getPriority(); + buildIfEmpty = false; + } + + public String getName() { + return name; + } + + public String getDocumentation() { + return documentation; + } + + public boolean isBuildIfEmpty() { + return buildIfEmpty; + } + + public String getEmptyNote() { + return emptyNote; + } + + public List getElements() { + return elements; + } + + public int getPriorty() { + return priorty; + } + + public int getCounter() { + return counter; + } + + public ElementTableGrouping getDefinition() { + return definition; + } + + + } + + public static class TableGroupSorter implements Comparator { + + @Override + public int compare(TableGroup o1, TableGroup o2) { + if (o1.priorty == o2.priorty) { + return Integer.compare(o1.counter, o2.counter); + } else { + return Integer.compare(o2.priorty, o1.priorty); // priorty sorts backwards + } + } + } + + private RenderingContext context; + private List groups; + private DataRenderer dr; + + public ElementTable(RenderingContext context, List groups, DataRenderer dr) { + this.context = context; + this.groups = groups; + this.dr = dr; + } + + public void build(HierarchicalTableGenerator gen, TableModel table) throws FHIRFormatError, DefinitionException, IOException { + Collections.sort(groups, new TableGroupSorter()); + table.setBorder(true); + table.setShowHeadings(false); + + + for (TableGroup grp : groups) { + if (grp.getElements().size() > 0 || grp.buildIfEmpty) { + renderGroup(gen, table, grp); + } + } + + } + + private void renderGroup(HierarchicalTableGenerator gen, TableModel table, TableGroup grp) throws FHIRFormatError, DefinitionException, IOException { + Row row = gen.new Row(); + table.getRows().add(row); + Cell cell = gen.new Cell(null, null, grp.getName(), null, null); + row.getCells().add(cell); + cell.span(3); + row.setColor("#dfdfdf"); + cell.addStyle("vertical-align: middle"); + cell.addStyle("font-weight: bold"); + cell.addStyle("font-size: 14px"); + cell.addStyle("padding-top: 10px"); + cell.addStyle("padding-bottom: 10px"); + + boolean first = true; + for (TableElement e : grp.elements) { + renderElement(gen, row.getSubRows(), e, first); + first = false; + } + } + + private void renderElement(HierarchicalTableGenerator gen, List rows, TableElement e, boolean first) throws FHIRFormatError, DefinitionException, IOException { + Row row = gen.new Row(); + rows.add(row); + if (!first) { + row.setTopLine("silver"); + } + renderElementIdentity(gen, row, e); + renderElementDefinition(gen, row, e); + renderElementConstraints(gen, row, e); + + +// if (e.invariants.size() > 0) { +// tr.style("border-bottom: none"); +// tr = table.tr(); +// tr.style("border-top: none"); +// tr.style("border-left: black 1px solid"); +// tr.style("border-right: black 1px solid"); +// renderElementInvariants(tr.td().colspan(3), e); +// } +// tr.style("border-bottom: silver 1px solid"); + + for (TableElement child : e.getChildElements()) { + renderElement(gen, row.getSubRows(), child, false); + } + } + + public void renderElementIdentity(HierarchicalTableGenerator gen, Row row, TableElement e) { + Cell cell = gen.new Cell(); + cell.addCellStyle("min-width: 220px"); + row.getCells().add(cell); + cell.setInnerTable(true); + cell.addText(e.getName()).addStyle("font-weight: bold"); + cell.addPiece(gen.new Piece("br")); + if ("1".equals(e.min) && "1".equals(e.max)) { + cell.addText("Required"); + } else if ("0".equals(e.min) && "*".equals(e.max)) { + cell.addText("Optional, Repeating"); + } else if ("0".equals(e.min) && "1".equals(e.max)) { + cell.addText("Optional"); + } else if ("1".equals(e.min) && "*".equals(e.max)) { + cell.addText("Repeating"); + } else { + cell.addText("Cardinality: "+e.min+".."+e.max); + } + cell.addPiece(gen.new Piece("br")); + cell.addImg(e.getTypeIcon(), e.getTypeHint(), e.getTypeLink()); + cell.addPiece(gen.new Piece(e.getTypeLink(), " "+e.getTypeName(), e.getTypeHint())); + } + + public void renderElementConstraints(HierarchicalTableGenerator gen, Row row, TableElement e) throws FHIRFormatError, DefinitionException, IOException { + Cell cell = gen.new Cell(); + cell.addCellStyle("min-width: 300px"); + row.getCells().add(cell); + XhtmlNode div = new XhtmlNode(NodeType.Element, "div"); + + Collections.sort(e.getConstraints(), new ConstraintsSorter()); + boolean first = true; + for (TableElementConstraint c : e.getConstraints()) { + if (first) first= false; else div.br(); + switch (c.type) { + case BINDING: + renderBindingConstraint(div, c); + break; + case CHOICE: + renderChoiceConstraint(div, c); + break; + case FIXED: + renderValueConstraint(div, c); + break; + case MAXLENGTH: + renderValueConstraint(div, c); + break; + case PATTERN: + renderValueConstraint(div, c); + break; + case PROFILE: + renderListConstraint(div, c); + break; + case RANGE: + renderRangeConstraint(div, c); + break; + case TARGET: + renderListConstraint(div, c); + break; + case CARDINALITY: + renderCardinalityConstraint(div, c); + break; + default: + break; + + } + } + cell.addXhtml(div); + } + + private void renderBindingConstraint(XhtmlNode x, TableElementConstraint c) { + String name = c.path == null ? "value" : c.path; + x.code().tx(name); + ValueSet vs = context.getContext().findTxResource(ValueSet.class, c.valueSet); + if (vs == null) { + x.tx(" is bound to an unknown valueset "); + x.code().tx(c.valueSet); + } else { + x.tx(" is bound to "); + x.ah(vs.getWebPath()).tx(vs.present()); + try { + ValueSetExpansionOutcome exp = context.getContext().expandVS(vs, true, false); + if (!exp.isOk()) { + x.span().attribute("title", exp.getError()).tx(" (??)"); + } else if (exp.getValueset().getExpansion().getContains().size() == 1000) { + x.tx(" (>1000 codes)"); + } else if (exp.getValueset().getExpansion().getContains().size() > 6) { + x.tx(" ("+exp.getValueset().getExpansion().getContains().size()+" codes)"); + } else { + x.tx(". Codes:"); + XhtmlNode ul = x.ul(); + for (ValueSetExpansionContainsComponent cc : exp.getValueset().getExpansion().getContains()) { + + String url = cc.hasSystem() && cc.hasCode() ? dr.getLinkForCode(cc.getSystem(), cc.getVersion(), cc.getCode()) : null; + var li = ul.li(); + li.ahOrNot(url).tx(dr.displayCodeSource(cc.getSystem(), cc.getVersion())+": "+cc.getCode()); + if (cc.hasDisplay()) { + li.tx(" \""+cc.getDisplay()+"\""); + } + } + } + } catch (Exception e) { + x.span().attribute("title", e.getMessage()).tx(" (??)"); + } + } + } + + private void renderChoiceConstraint(XhtmlNode x, TableElementConstraint c) { + String name = c.path == null ? "value" : c.path; + x.code().tx(name); + x.tx(" is a choice of:"); + var ul = x.ul(); + for (TypeRefComponent tr : c.types) { + if (tr.hasProfile()) { + for (CanonicalType ct : tr.getProfile()) { + StructureDefinition sd = context.getContext().fetchTypeDefinition(ct.primitiveValue()); + if (sd == null || !sd.hasWebPath()) { + ul.li().ah(ct.primitiveValue()).tx(ct.primitiveValue()); + } else { + ul.li().ah(sd.getWebPath()).tx(sd.present()); + } + } + } else if (tr.hasTarget()) { + StructureDefinition sd = context.getContext().fetchTypeDefinition(tr.getWorkingCode()); + var li = ul.li(); + li.ah(sd.getWebPath()).tx(sd.present()); + li.tx(" pointing to "); + renderTypeList(x, tr.getTargetProfile()); + + } else { + StructureDefinition sd = context.getContext().fetchTypeDefinition(tr.getWorkingCode()); + if (sd == null || !sd.hasWebPath()) { + ul.li().code().tx(tr.getWorkingCode()); + } else { + ul.li().ah(sd.getWebPath()).tx(sd.present()); + } + } + } + + + } + + private void renderValueConstraint(XhtmlNode x, TableElementConstraint c) throws FHIRFormatError, DefinitionException, IOException { + String name = c.path == null ? "value" : c.path; + x.code().tx(name); + switch (c.type) { + case FIXED: + x.tx(" is fixed to "); + break; + case MAXLENGTH: + x.tx(" is limited in length to "); + + break; + case PATTERN: + if (c.value.isPrimitive()) { + x.tx(" is fixed to "); + } else { + x.tx(" must match "); + } + break; + default: + break; + } + renderValue(x, c.value); + } + + public void renderValue(XhtmlNode x, DataType v) throws IOException { + if (v.isPrimitive()) { + String s = v.primitiveValue(); + if (Utilities.isAbsoluteUrl(s)) { + Resource res = context.getContext().fetchResource(Resource.class, s); + if (res != null && res.hasWebPath()) { + x.ah(res.getWebPath()).tx(res instanceof CanonicalResource ? ((CanonicalResource) res).present() : s); + } else if (Utilities.isAbsoluteUrlLinkable(s)) { + x.ah(s).code().tx(s); + } else { + x.code().tx(s); + } + } else { + x.code().tx(s); + } + } else if (v instanceof Quantity) { + genQuantity(x, (Quantity) v); + } else if (v instanceof Coding) { + genCoding(x, (Coding) v); + } else if (v instanceof CodeableConcept) { + genCodeableConcept(x, (CodeableConcept) v); + } else { + dr.renderBase(new RenderingStatus(), x, v); + } + } + + private void genCodeableConcept(XhtmlNode div, CodeableConcept cc) { + boolean first = true; + for (Coding c : cc.getCoding()) { + if (first) first = false; else div.tx(","); + genCoding(div, c); + } + if (cc.hasText()) { + div.code().tx(" \""+cc.getText()+"\""); + } + + } + + public void genQuantity(XhtmlNode div, Quantity q) { + String url = q.hasSystem() && q.hasUnit() ? dr.getLinkForCode(q.getSystem(), null, q.getCode()) : null; + var code = div.code(); + if (q.hasComparator()) { + code.tx(q.getComparator().toCode()); + } + code.tx(q.getValueElement().asStringValue()); + code.ahOrNot(url).tx(q.getUnit()); + } + + public void genCoding(XhtmlNode div, Coding c) { + String url = c.hasSystem() && c.hasCode() ? dr.getLinkForCode(c.getSystem(), c.getVersion(), c.getCode()) : null; + var code = div.code(); + code.ahOrNot(url).tx(dr.displayCodeSource(c.getSystem(), c.getVersion())+": "+c.getCode()); + if (c.hasDisplay()) { + code.tx(" \""+c.getDisplay()+"\""); + } else { + String s = dr.lookupCode(c.getSystem(), c.getVersion(), c.getCode()); + if (s != null) { + div.span().style("opacity: 0.5").tx("(\""+s+"\")"); + } + } + } + + private void renderRangeConstraint(XhtmlNode x, TableElementConstraint c) throws IOException { + String name = c.path == null ? "value" : c.path; + if (c.value != null && c.value2 != null) { + x.tx(name + " between "); + renderValue(x, c.value); + x.tx(" and " ); + renderValue(x, c.value2); + } else if (c.value != null) { + x.tx(name + " more than "); + renderValue(x, c.value); + } else { + x.tx(name + " less than "); + renderValue(x, c.value2); + } + } + + private void renderCardinalityConstraint(XhtmlNode x, TableElementConstraint c) throws IOException { + String name = c.path == null ? "value" : c.path; + x.code().tx(name); + String min = c.value.primitiveValue(); + String max = c.value2.primitiveValue(); + if ("1".equals(min) && "1".equals(max)) { + x.tx("is required"); + } else if ("0".equals(min) && "*".equals(max)) { + x.tx("is Optional and repeats"); + } else if ("0".equals(min) && "1".equals(max)) { + x.tx("is Optional"); + } else if ("1".equals(min) && "*".equals(max)) { + x.tx("repeats"); + } else { + x.tx("has cardinality: "+min+".."+max); + } + } + + private void renderListConstraint(XhtmlNode x, TableElementConstraint c) { + String name = c.path == null ? "value" : c.path; + + x.code().tx(name); + switch (c.type) { + case PROFILE: + x.tx(" must be "); + break; + case TARGET: + x.tx(" must point to "); + break; + default: + break; + } + renderTypeList(x, c.list); + } + + private void renderTypeList(XhtmlNode x, List list) { + + if (list.size() == 1) { + x.tx("a "); + } else { + x.tx("one of "); + } + boolean first = true; + for (int i = 0; i < list.size(); i++) { + if (first) { + first = false; + } else if (i == list.size() - 1) { + x.tx(" or " ); + } else { + x.tx(", "); + } + String s = list.get(i).primitiveValue(); + Resource res = context.getContext().fetchResource(Resource.class, s); + if (res != null && res.hasWebPath()) { + x.ah(res.getWebPath()).tx(res instanceof CanonicalResource ? ((CanonicalResource) res).present() : s); + } else { + x.ah(s).tx(s); + } + } + } + + public void renderElementDefinition(HierarchicalTableGenerator gen, Row row, TableElement e) { + Cell cell = gen.new Cell(); row.getCells().add(cell); + for (TableElementDefinition d : e.definitions) { + if (d.getType() == TableElementDefinitionType.DEFINITION) { + cell.addMarkdown(d.getMarkdown()); + } else if (d.getType() == TableElementDefinitionType.COMMENT) { + cell.addMarkdown("Comment: "+d.getMarkdown(), "font-style: italic"); + } + } + } + + private void renderElementInvariants(XhtmlNode td, TableElement e) { + XhtmlNode ul = td.ul(); + for (TableElementInvariant t : e.invariants) { + var li = ul.li(); + li.tx(t.level+": "+t.human); + li.tx(" "); + li.code().tx(t.other != null ? t.other : t.fhirPath); + } + + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java index f4df2975f..d9dd3077a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java @@ -160,6 +160,8 @@ public class TerminologyClientManager { private ILoggingService logger; + private int ecosystemfailCount; + public TerminologyClientManager(ITerminologyClientFactory factory, String cacheId, ILoggingService logger) { super(); this.factory = factory; @@ -566,29 +568,46 @@ public class TerminologyClientManager { if (!useEcosystem) { server = getMasterClient().getAddress(); } else { - if (usage != null) { - request = request + "&usage="+usage; - } - JsonObject json = JsonParser.parseObjectFromUrl(request); - for (JsonObject item : json.getJsonObjects("authoritative")) { - if (server == null) { - server = item.asString("url"); + ecosystemfailCount = 0; + try { + if (usage != null) { + request = request + "&usage="+usage; } - } - for (JsonObject item : json.getJsonObjects("candidates")) { - if (server == null) { - server = item.asString("url"); + JsonObject json = JsonParser.parseObjectFromUrl(request); + for (JsonObject item : json.getJsonObjects("authoritative")) { + if (server == null) { + server = item.asString("url"); + } + } + for (JsonObject item : json.getJsonObjects("candidates")) { + if (server == null) { + server = item.asString("url"); + } + } + if (server == null) { + server = getMasterClient().getAddress(); + } + if (server.contains("://tx.fhir.org")) { + try { + server = server.replace("tx.fhir.org", host()); + } catch (MalformedURLException e) { + } + } + } catch (Exception e) { + // the ecosystem cal failed, so we're just going to fall back to + String msg = "Error resolving valueSet "+canonical+": "+e.getMessage(); + if (!hasMessage(msg)) { + internalLog.add(new InternalLogEvent(msg, canonical, request)); + } + if (logger.isDebugLogging()) { + e.printStackTrace(); + } + ecosystemfailCount++; + if (ecosystemfailCount > 3) { + useEcosystem = false; } - } - if (server == null) { server = getMasterClient().getAddress(); } - if (server.contains("://tx.fhir.org")) { - try { - server = server.replace("tx.fhir.org", host()); - } catch (MalformedURLException e) { - } - } } TerminologyClientContext client = serverMap.get(server); if (client == null) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/testfactory/TestDataFactory.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/testfactory/TestDataFactory.java index 56f028281..8ae3b3f7b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/testfactory/TestDataFactory.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/testfactory/TestDataFactory.java @@ -82,6 +82,17 @@ public class TestDataFactory { return FhirPublication.R5; } + public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { + if (rows != null && "rows".equals(name)) { + Base[] l = new Base[rows.size()]; + for (int i = 0; i < rows.size(); i++) { + l[i] = BaseTableWrapper.forRow(columns, rows.get(i)); + } + return l; + } + return super.getProperty(hash, name, checkValid); + } + public String cell(int row, String col) { if (row >= 0 && row < rows.size()) { List r = rows.get(row); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceSorters.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceSorters.java index fa9608a6b..c1bf091f3 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceSorters.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceSorters.java @@ -10,10 +10,12 @@ public class ResourceSorters { @Override public int compare(CanonicalResource arg0, CanonicalResource arg1) { - if (arg0.getUrl() != null) { + if (arg0.getUrl() != null && arg1.getUrl() != null) { return arg0.getUrl().compareTo(arg1.getUrl()); } else if (arg1.getUrl() != null) { - return -arg1.getUrl().compareTo(arg0.getUrl()); + return -1; + } else if (arg0.getUrl() != null) { + return 1; } else { return 0; } 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 c9d685ede..4eba72976 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 @@ -278,6 +278,8 @@ public class ToolingExtensions { public static final String EXT_ALTERNATE_CANONICAL = "http://hl7.org/fhir/StructureDefinition/alternate-canonical"; public static final String EXT_SUPPRESSED = "http://hl7.org/fhir/StructureDefinition/elementdefinition-suppress"; public static final String EXT_SUPPRESS_RESOURCE_TYPE = "http://hl7.org/fhir/tools/StructureDefinition/json-suppress-resourcetype"; + public static final String EXT_PROFILE_VIEW_HINT = "http://hl7.org/fhir/tools/StructureDefinition/view-hint"; + public static final String EXT_SNAPSHOT_BEHAVIOR = "http://hl7.org/fhir/tools/StructureDefinition/snapshot-behavior"; // specific extension helpers @@ -474,6 +476,7 @@ public class ToolingExtensions { } return null; } + public static String readStringExtension(DomainResource c, String uri) { Extension ex = getExtension(c, uri); if (ex == null) @@ -495,6 +498,30 @@ public class ToolingExtensions { return null; } + public static String readStringSubExtension(DomainResource c, String uri, String name) { + Extension ex = getExtension(c, uri); + if (ex == null) + return null; + ex = getExtension(ex, name); + if (ex == null) + return null; + if ((ex.getValue() instanceof StringType)) + return ((StringType) ex.getValue()).getValue(); + if ((ex.getValue() instanceof UriType)) + return ((UriType) ex.getValue()).getValue(); + if (ex.getValue() instanceof CodeType) + return ((CodeType) ex.getValue()).getValue(); + if (ex.getValue() instanceof IntegerType) + return ((IntegerType) ex.getValue()).asStringValue(); + if (ex.getValue() instanceof Integer64Type) + return ((Integer64Type) ex.getValue()).asStringValue(); + if (ex.getValue() instanceof DecimalType) + return ((DecimalType) ex.getValue()).asStringValue(); + if ((ex.getValue() instanceof MarkdownType)) + return ((MarkdownType) ex.getValue()).getValue(); + return null; + } + @SuppressWarnings("unchecked") public static PrimitiveType readPrimitiveExtension(DomainResource c, String uri) { Extension ex = getExtension(c, uri); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/UserDataNames.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/UserDataNames.java index 0f33c2419..ff9dbe062 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/UserDataNames.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/UserDataNames.java @@ -17,6 +17,7 @@ public class UserDataNames { public static final String SNAPSHOT_DERIVATION_POINTER = "derived.pointer"; public static final String SNAPSHOT_IS_DERIVED = "derived.fact"; public static final String SNAPSHOT_GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed"; + public static final String SNAPSHOT_DERIVATION_DIFF = "profileutilities.snapshot.diffsource"; public static final String SNAPSHOT_GENERATING = "profileutils.snapshot.generating"; public static final String SNAPSHOT_GENERATED = "profileutils.snapshot.generated"; public static final String SNAPSHOT_GENERATED_MESSAGES = "profileutils.snapshot.generated.messages"; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index e44ddd998..894a4638c 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -1163,4 +1163,6 @@ public class I18nConstants { public static final String VS_EXP_IMPORT_FAIL_X = "VS_EXP_IMPORT_FAIL_X"; public static final String VS_EXP_FILTER_UNK = "VS_EXP_FILTER_UNK"; public static final String NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_ERR = "NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_ERR"; + public static final String CONCEPTMAP_VS_NOT_A_VS = "CONCEPTMAP_VS_NOT_A_VS"; + public static final String SD_DERIVATION_NO_CONCRETE = "SD_DERIVATION_NO_CONCRETE"; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/RenderingI18nContext.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/RenderingI18nContext.java index f8c0e34c3..4b054001d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/RenderingI18nContext.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/RenderingI18nContext.java @@ -1,5 +1,6 @@ package org.hl7.fhir.utilities.i18n; + public class RenderingI18nContext extends I18nBase { public static final String ACTOR_DEF_ACT = "ACTOR_DEF_ACT"; @@ -931,6 +932,19 @@ public class RenderingI18nContext extends I18nBase { public static final String MATURITY_MATURITY = "MATURITY_MATURITY"; public static final String MATURITY_STDS_STATUS = "MATURITY_STDS_STATUS"; + public static final String DATA_REND_MORNING = "DATA_REND_MORNING"; + public static final String DATA_REND_MORNING_EARLY = "DATA_REND_MORNING_EARLY"; + public static final String DATA_REND_MORNING_LATE = "DATA_REND_MORNING_LATE"; + public static final String DATA_REND_NOON = "DATA_REND_NOON"; + public static final String DATA_REND_AFTERNOON = "DATA_REND_AFTERNOON"; + public static final String DATA_REND_AFTERNOON_EARLY = ""; + public static final String DATA_REND_AFTERNOON_LATE = "DATA_REND_AFTERNOON_LATE"; + public static final String DATA_REND_EVENING = "DATA_REND_EVENING"; + public static final String DATA_REND_EVENING_EARLY = ""; + public static final String DATA_REND_EVENING_LATE = "DATA_REND_EVENING_LATE"; + public static final String DATA_REND_NIGHT = "DATA_REND_NIGHT"; + public static final String DATA_REND_AFTER_SLEEP = "DATA_REND_AFTER_SLEEP"; + public static final String DATA_REND_IMMEDIATE = "DATA_REND_IMMEDIATE"; protected String getMessagesSourceFileName() { return "rendering-phrases"; 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 d743f1c7a..0554f12de 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 @@ -250,6 +250,7 @@ public class HierarchicalTableGenerator { private List pieces = new ArrayList(); private String cellStyle; protected int span = 1; + private boolean innerTable; // if you want a multiline left cell, you have to set this to true private TextAlignment alignment = TextAlignment.LEFT; private String id; @@ -271,17 +272,19 @@ public class HierarchicalTableGenerator { pieces.add(piece); return this; } - - public Cell addMarkdown(String md) { + return addMarkdown(md, null); + } + + public Cell addMarkdown(String md, String style) { if (!Utilities.noString(md)) { try { Parser parser = Parser.builder().build(); Node document = parser.parse(md); HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); String html = renderer.render(document); - pieces.addAll(htmlToParagraphPieces(html, null)); + pieces.addAll(htmlToParagraphPieces(html, style)); } catch (Exception e) { e.printStackTrace(); } @@ -405,12 +408,22 @@ public class HierarchicalTableGenerator { } return piece; } - + public Cell addStyle(String style) { for (Piece p : pieces) p.addStyle(style); return this; } + + public Cell addCellStyle(String style) { + if (cellStyle == null) { + cellStyle = style; + } else { + cellStyle = cellStyle+"; "+style; + } + return this; + } + public void addToHint(String text) { for (Piece p : pieces) p.addToHint(text); @@ -472,6 +485,25 @@ public class HierarchicalTableGenerator { public void setId(String id) { this.id = id; } + public Piece addImg(String icon, String hint, String link) { + Piece p = new Piece("img"); + p.attr("src", icon); + p.hint = hint; + p.reference = link; + pieces.add(p); + return p; + } + public void addXhtml(XhtmlNode div) { + Piece p = new Piece(null); + pieces.add(p); + p.children = div.childNodes; + } + public boolean isInnerTable() { + return innerTable; + } + public void setInnerTable(boolean innerTable) { + this.innerTable = innerTable; + } } @@ -505,6 +537,8 @@ public class HierarchicalTableGenerator { private int lineColor; private String id; private String opacity; + private String topLine; + private boolean partnerRow; public List getSubRows() { return subRows; @@ -561,6 +595,13 @@ public class HierarchicalTableGenerator { } return b.toString(); } + public String getTopLine() { + return topLine; + } + public void setTopLine(String topLine) { + this.topLine = topLine; + } + } public class TableModel { @@ -571,6 +612,8 @@ public class HierarchicalTableGenerator { private String docoRef; private String docoImg; private boolean alternating; + private boolean showHeadings = true; + private boolean border = false; public TableModel(String id, boolean active) { super(); @@ -611,6 +654,18 @@ public class HierarchicalTableGenerator { public void setAlternating(boolean alternating) { this.alternating = alternating; } + public boolean isShowHeadings() { + return showHeadings; + } + public void setShowHeadings(boolean showHeadings) { + this.showHeadings = showHeadings; + } + public boolean isBorder() { + return border; + } + public void setBorder(boolean border) { + this.border = border; + } } @@ -630,6 +685,7 @@ public class HierarchicalTableGenerator { private TableGenerationMode mode; private RenderingI18nContext i18n; private String uniqueLocalPrefix; + private boolean treelines = true; public HierarchicalTableGenerator(RenderingI18nContext i18n) { super(); @@ -724,6 +780,7 @@ public class HierarchicalTableGenerator { return model; } + public TableModel initComparisonTable(String prefix, String id) throws IOException { TableModel model = new TableModel(id, true); @@ -762,38 +819,45 @@ public class HierarchicalTableGenerator { public XhtmlNode generate(TableModel model, String imagePath, int border, Set outputTracker) throws IOException, FHIRException { checkModel(model); XhtmlNode table = new XhtmlNode(NodeType.Element, "table").setAttribute("border", Integer.toString(border)).setAttribute("cellspacing", "0").setAttribute("cellpadding", "0"); - + table.setAttribute("fhir", "generated-heirarchy"); if (model.isActive()) { table.setAttribute("id", model.getId()); } - table.setAttribute("style", "border: " + border + "px #F0F0F0 solid; font-size: 11px; font-family: verdana; vertical-align: top;"); - XhtmlNode tr = table.addTag("tr"); - tr.setAttribute("style", "border: " + Integer.toString(1 + border) + "px #F0F0F0 solid; font-size: 11px; font-family: verdana; vertical-align: top"); - XhtmlNode tc = null; - for (Title t : model.getTitles()) { - tc = renderCell(tr, t, "th", null, null, null, false, null, "white", 0, imagePath, border, outputTracker, model, null, true); - if (t.width != 0) - tc.setAttribute("style", "width: "+Integer.toString(t.width)+"px"); + if (model.isBorder()) { + table.setAttribute("style", "border: 2px black solid; font-size: 11px; font-family: verdana; vertical-align: top;"); + } else { + table.setAttribute("style", "border: " + border + "px #F0F0F0 solid; font-size: 11px; font-family: verdana; vertical-align: top;"); } - if (tc != null && model.getDocoRef() != null) { - XhtmlNode a = tc.addTag("span").setAttribute("style", "float: right").addTag("a").setAttribute("title", "Legend for this format").setAttribute("href", model.getDocoRef()); - if (mode == TableGenerationMode.XHTML) { - a.setAttribute("no-external", "true"); + if (model.isShowHeadings()) { + XhtmlNode tr = table.addTag("tr"); + tr.setAttribute("fhir", "generated-heirarchy"); + tr.setAttribute("style", "border: " + Integer.toString(1 + border) + "px #F0F0F0 solid; font-size: 11px; font-family: verdana; vertical-align: top"); + XhtmlNode tc = null; + for (Title t : model.getTitles()) { + tc = renderCell(tr, t, "th", null, null, null, false, null, "white", 0, imagePath, border, outputTracker, model, null, true); + if (t.width != 0) + tc.setAttribute("style", "width: "+Integer.toString(t.width)+"px"); } - XhtmlNode img = a.addTag("img"); - img.setAttribute("alt", "doco").setAttribute("style", "background-color: inherit").setAttribute("src", model.getDocoImg()); - if (model.isActive()) { - img.setAttribute("onLoad", "fhirTableInit(this)"); + if (tc != null && model.getDocoRef() != null) { + XhtmlNode a = tc.addTag("span").setAttribute("style", "float: right").addTag("a").setAttribute("title", "Legend for this format").setAttribute("href", model.getDocoRef()); + if (mode == TableGenerationMode.XHTML) { + a.setAttribute("no-external", "true"); + } + XhtmlNode img = a.addTag("img"); + img.setAttribute("alt", "doco").setAttribute("style", "background-color: inherit").setAttribute("src", model.getDocoImg()); + if (model.isActive()) { + img.setAttribute("onLoad", "fhirTableInit(this)"); + } } } - Counter counter = new Counter(); for (Row r : model.getRows()) { renderRow(table, r, 0, new ArrayList(), imagePath, border, outputTracker, counter, model); } if (model.getDocoRef() != null) { - tr = table.addTag("tr"); - tc = tr.addTag("td"); + XhtmlNode tr = table.addTag("tr"); + tr.setAttribute("fhir", "generated-heirarchy"); + XhtmlNode tc = tr.addTag("td"); tc.setAttribute("class", "hierarchy"); tc.setAttribute("colspan", Integer.toString(model.getTitles().size())); tc.addTag("br"); @@ -807,15 +871,21 @@ public class HierarchicalTableGenerator { private void renderRow(XhtmlNode table, Row r, int indent, List indents, String imagePath, int border, Set outputTracker, Counter counter, TableModel model) throws IOException { - counter.row(); + if (!r.partnerRow) { + counter.row(); + } XhtmlNode tr = table.addTag("tr"); + tr.setAttribute("fhir", "generated-heirarchy"); + String color = "white"; if (r.getColor() != null) color = r.getColor(); else if (model.isAlternating() && counter.isOdd()) color = BACKGROUND_ALT_COLOR; - tr.setAttribute("style", "border: " + border + "px #F0F0F0 solid; padding:0px; vertical-align: top; background-color: "+color+(r.getOpacity() == null ? "" : "; opacity: "+r.getOpacity())); + String lineStyle = r.getTopLine() == null ? "" : "; border-top: 1px solid "+r.getTopLine(); + + tr.setAttribute("style", "border: " + border + "px #F0F0F0 solid; padding:0px; vertical-align: top; background-color: "+color+(r.getOpacity() == null ? "" : "; opacity: "+r.getOpacity())+lineStyle); if (model.isActive()) { tr.setAttribute("id", r.getId()); } @@ -848,26 +918,34 @@ public class HierarchicalTableGenerator { } if (c.getId() != null) { tc.setAttribute("id", c.getId()); + } + String lineStyle = row != null && row.getTopLine() == null ? "" : "; padding-top: 3px; padding-bottom: 3px"; + + XhtmlNode itc = tc; + XhtmlNode itr = null; + if (c.innerTable) { + itr = tc.table("none").tr(); + itc = itr.td(); } if (indents != null) { - tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_spacer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); - tc.setAttribute("style", "vertical-align: top; text-align : left; "+(c.cellStyle != null && c.cellStyle.contains("background-color") ? "" : "background-color: "+color+"; ")+"border: "+ border +"px #F0F0F0 solid; padding:0px 4px 0px 4px; white-space: nowrap; background-image: url("+imagePath+checkExists(indents, hasChildren, lineColor, outputTracker)+")"+(c.cellStyle != null ? ";"+c.cellStyle : "")); + itc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_spacer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); + tc.setAttribute("style", "vertical-align: top; text-align : left; "+(c.cellStyle != null && c.cellStyle.contains("background-color") ? "" : "background-color: "+color+"; ")+"border: "+ border +"px #F0F0F0 solid; padding:0px 4px 0px 4px; white-space: nowrap"+(treelines ? "; background-image: url("+imagePath+checkExists(indents, hasChildren, lineColor, outputTracker)+")" : "")+(c.cellStyle != null ? ";"+c.cellStyle : "")+lineStyle); for (int i = 0; i < indents.size()-1; i++) { switch (indents.get(i)) { case NEW_REGULAR: case NEW_SLICER: case NEW_SLICE: - tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_blank.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); + itc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_blank.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); break; case CONTINUE_REGULAR: - tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); + itc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); break; case CONTINUE_SLICER: - tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline_slicer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); + itc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline_slicer.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); break; case CONTINUE_SLICE: - tc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline_slice.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); + itc.addTag("img").setAttribute("src", srcFor(imagePath, "tbl_vline_slice.png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); break; default: throw new Error("Unrecognized indent level: " + indents.get(i)); @@ -875,7 +953,7 @@ public class HierarchicalTableGenerator { } if (!indents.isEmpty()) { String sfx = table.isActive() && hasChildren ? "-open" : ""; - XhtmlNode img = tc.addTag("img"); + XhtmlNode img = itc.addTag("img"); switch (indents.get(indents.size()-1)) { case NEW_REGULAR: img.setAttribute("src", srcFor(imagePath, "tbl_vjoin_end"+sfx+".png")).setAttribute("style", "background-color: inherit").setAttribute("class", "hierarchy").setAttribute("alt", "."); @@ -903,17 +981,21 @@ public class HierarchicalTableGenerator { } } } - else - tc.setAttribute("style", "vertical-align: top; text-align : left; "+(c.cellStyle != null && c.cellStyle.contains("background-color") ? "" : "background-color: "+color+"; ")+"border: "+ border +"px #F0F0F0 solid; padding:0px 4px 0px 4px"+(c.cellStyle != null ? ";"+c.cellStyle : "")); + else { + tc.setAttribute("style", "vertical-align: top; text-align : left; "+(c.cellStyle != null && c.cellStyle.contains("background-color") ? "" : "background-color: "+color+"; ")+"border: "+ border +"px #F0F0F0 solid; padding:0px 4px 0px 4px"+(c.cellStyle != null ? ";"+c.cellStyle : "")+lineStyle); + } + if (c.innerTable) { + itc = itr.td(); + } if (!Utilities.noString(icon)) { - XhtmlNode img = tc.addTag("img").setAttribute("alt", "icon").setAttribute("src", srcFor(imagePath, icon)).setAttribute("class", "hierarchy").setAttribute("style", "background-color: "+color+"; background-color: inherit").setAttribute("alt", "."); + XhtmlNode img = itc.addTag("img").setAttribute("alt", "icon").setAttribute("src", srcFor(imagePath, icon)).setAttribute("class", "hierarchy").setAttribute("style", "background-color: "+color+"; background-color: inherit").setAttribute("alt", "."); if (hint != null) img.setAttribute("title", hint); - tc.addText(" "); + itc.addText(" "); } for (Piece p : c.pieces) { if (!Utilities.noString(p.getTag())) { - XhtmlNode tag = tc.addTag(p.getTag()); + XhtmlNode tag = itc.addTag(p.getTag()); if (p.attributes != null) for (String n : p.attributes.keySet()) tag.setAttribute(n, p.attributes.get(n)); @@ -924,7 +1006,7 @@ public class HierarchicalTableGenerator { tag.addChildNodes(p.getChildren()); } } else if (!Utilities.noString(p.getReference())) { - XhtmlNode a = addStyle(tc.addTag("a"), p); + XhtmlNode a = addStyle(itc.addTag("a"), p); a.setAttribute("href", prefixLocalHref(p.getReference())); if (mode == TableGenerationMode.XHTML && suppressExternals) { a.setAttribute("no-external", "true"); @@ -943,25 +1025,25 @@ public class HierarchicalTableGenerator { } if (p.hasChildren()) { - tc.addChildNodes(p.getChildren()); + itc.addChildNodes(p.getChildren()); } } else { if (!Utilities.noString(p.getHint())) { - XhtmlNode s = addStyle(tc.addTag("span"), p); + XhtmlNode s = addStyle(itc.addTag("span"), p); s.setAttribute("title", p.getHint()); s.addText(p.getText()); } else if (p.getStyle() != null) { - XhtmlNode s = addStyle(tc.addTag("span"), p); + XhtmlNode s = addStyle(itc.addTag("span"), p); s.addText(p.getText()); } else { - tc.addText(p.getText()); + itc.addText(p.getText()); } if (p.hasChildren()) { - tc.addChildNodes(p.getChildren()); + itc.addChildNodes(p.getChildren()); } if (p.getTagImg() != null) { - tc.tx(" "); - tc.img(p.getTagImg(), null); + itc.tx(" "); + itc.img(p.getTagImg(), null); } } } @@ -983,6 +1065,15 @@ public class HierarchicalTableGenerator { } private String srcFor(String corePrefix, String filename) throws IOException { + if (!treelines && filename.startsWith("tbl")) { + if (filename.contains("-open")) { + filename = "tbl-open.png"; + } else if (filename.contains("-closed")) { + filename = "tbl-closed.png"; + } else { + filename = "tbl_blank.png"; + } + } if (inLineGraphics) { if (files.containsKey(filename)) return files.get(filename); @@ -991,14 +1082,15 @@ public class HierarchicalTableGenerator { byte[] bytes; File file = ManagedFileAccess.file(Utilities.path(dest, filename)); if (!file.exists()) // because sometime this is called real early before the files exist. it will be built again later because of this - bytes = new byte[0]; + bytes = new byte[0]; else bytes = FileUtils.readFileToByteArray(file); b.append(new String(Base64.encodeBase64(bytes))); -// files.put(filename, b.toString()); + // files.put(filename, b.toString()); return b.toString(); - } else + } else { return corePrefix+filename; + } } public static String help16AsData() throws IOException { @@ -1072,7 +1164,7 @@ public class HierarchicalTableGenerator { b.append(new String(encodeBase64)); files.put(filename, b.toString()); return b.toString(); - } else { + } else if (treelines) { b.append("tbl_bck"); for (Integer i : indents) b.append(Integer.toString(i)); @@ -1098,6 +1190,8 @@ public class HierarchicalTableGenerator { } } return b.toString(); + } else { + return "tbl_bck0.png"; } } @@ -1177,5 +1271,13 @@ public class HierarchicalTableGenerator { } return "#"+uniqueLocalPrefix+"-"+url.substring(1); } + + public boolean isTreelines() { + return treelines; + } + + public void setTreelines(boolean treelines) { + this.treelines = treelines; + } } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 8aeb73438..e350e1a4b 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -1196,4 +1196,6 @@ VS_EXP_IMPORT_ERROR_X = Unable to expand excluded value set ''{0}'': {1} VS_EXP_IMPORT_ERROR_X = Unable to expand excluded value set ''{0}'', but no error VS_EXP_IMPORT_ERROR_TOO_COSTLY = Unable to expand excluded value set ''{0}'': too costly VS_EXP_FILTER_UNK = ValueSet ''{0}'' Filter by property ''{1}'' and op ''{2}'' is not supported yet - \ No newline at end of file +CONCEPTMAP_VS_NOT_A_VS = Reference must be to a ValueSet, but found a {0} instead +SD_DERIVATION_NO_CONCRETE = {0} is labeled as an abstract type, but no concrete descendents were found (check definitions - this is usually an error unless concrete definitions are in some other package) + \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/resources/rendering-phrases.properties b/org.hl7.fhir.utilities/src/main/resources/rendering-phrases.properties index aed58115a..cfb7839b1 100644 --- a/org.hl7.fhir.utilities/src/main/resources/rendering-phrases.properties +++ b/org.hl7.fhir.utilities/src/main/resources/rendering-phrases.properties @@ -934,3 +934,16 @@ MATURITY_PUBLISHER = Publisher: {0} MATURITY_STATUS = Status MATURITY_MATURITY = Maturity Level: {0} MATURITY_STDS_STATUS = Standards Status: {0} +DATA_REND_MORNING = Morning +DATA_REND_MORNING_EARLY = Early Morning +DATA_REND_MORNING_LATE = Late Morning +DATA_REND_NOON = Noon +DATA_REND_AFTERNOON = Afternoon +DATA_REND_AFTERNOON_EARLY = Early Afternoon +DATA_REND_AFTERNOON_LATE = Late Afternoon +DATA_REND_EVENING = Evening +DATA_REND_EVENING_EARLY = Early Evening +DATA_REND_EVENING_LATE = Late Evening +DATA_REND_NIGHT = Night +DATA_REND_AFTER_SLEEP = After Sleep +DATA_REND_IMMEDIATE = Immediate diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/TxTestsTask.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/TxTestsTask.java index a3e8ab5bc..3a9ce8891 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/TxTestsTask.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/TxTestsTask.java @@ -50,7 +50,7 @@ public class TxTestsTask extends StandaloneTask{ output = Utilities.path("[tmp]"); } boolean ok = new TxTester(new TxTester.InternalTxLoader(version), tx, false, loadExternals(externals)).setOutput(output).execute(cliContext.getModeParams(), filter); - SystemExitManager.setError(ok ? 1 : 0); + SystemExitManager.setError(ok ? 0 : 1); SystemExitManager.finish(); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java index 1246af2f9..c318dd9f0 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java @@ -526,7 +526,7 @@ public class CodeSystemValidator extends BaseValidator { } private void metaChecks(List errors, Element cs, NodeStack stack, String url, String content, String caseSensitive, String hierarchyMeaning, boolean isSupplement, int count, String supp) { - if (forPublication && (url.contains("hl7.org"))) { + if (forPublication && url != null && (url.contains("hl7.org"))) { hint(errors, "2024-03-07", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), url.contains("terminology.hl7.org") || url.contains("hl7.org/cda/stds/core"), I18nConstants.CODESYSTEM_THO_CHECK); } if (isSupplement) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ConceptMapValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ConceptMapValidator.java index 39e5c3221..5f41a3f37 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ConceptMapValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ConceptMapValidator.java @@ -11,6 +11,7 @@ import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; +import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; @@ -144,8 +145,10 @@ public class ConceptMapValidator extends BaseValidator { } } } - VSReference sourceScope = readVSReference(cm, "sourceScope", "source"); - VSReference targetScope = readVSReference(cm, "targetScope", "target"); + BooleanHolder bh = new BooleanHolder(); + VSReference sourceScope = readVSReference(errors, stack, bh, cm, "sourceScope", "source"); + VSReference targetScope = readVSReference(errors, stack, bh, cm, "targetScope", "target"); + ok = ok && bh.ok(); List groups = cm.getChildrenByName("group"); int ci = 0; @@ -191,7 +194,7 @@ public class ConceptMapValidator extends BaseValidator { } - private VSReference readVSReference(Element cm, String... names) { + private VSReference readVSReference(List errors, NodeStack stack,BooleanHolder bok, Element cm, String... names) { for (String n : names) { if (cm.hasChild(n, false)) { Element e = cm.getNamedChild(n, false); @@ -206,10 +209,32 @@ public class ConceptMapValidator extends BaseValidator { if (ref.contains("|")) { res.url = ref.substring(0, ref.indexOf("|")); res.version = ref.substring(ref.indexOf("|")+1); - res.vs = context.findTxResource(ValueSet.class, res.url, res.version); + Resource r = context.fetchResource(Resource.class, res.url, res.version); + if (r != null) { + if (r instanceof ValueSet) { + res.vs = (ValueSet) r; + } else { + bok.fail(); + rule(errors, "2025-12-31", IssueType.INVALID, stack.getLiteralPath()+"."+n, false, I18nConstants.CONCEPTMAP_VS_NOT_A_VS, r.fhirType()); + } + } + if (res.vs == null) { + res.vs = context.findTxResource(ValueSet.class, res.url, res.version); + } } else { res.url = ref; - res.vs = context.findTxResource(ValueSet.class, res.url); + Resource r = context.fetchResource(Resource.class, res.url); + if (r != null) { + if (r instanceof ValueSet) { + res.vs = (ValueSet) r; + } else { + bok.fail(); + rule(errors, "2025-12-31", IssueType.INVALID, stack.getLiteralPath()+"."+n, false, I18nConstants.CONCEPTMAP_VS_NOT_A_VS, r.fhirType()); + } + } + if (res.vs == null) { + res.vs = context.findTxResource(ValueSet.class, res.url); + } } return res; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index 9da107df1..13c44d27a 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -162,6 +162,19 @@ public class StructureDefinitionValidator extends BaseValidator { warning(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), !base.getExperimental() || experimental, I18nConstants.SD_BASE_EXPERIMENTAL, sd.getBaseDefinition()); } + String abstractV = src.getNamedChildValue("abstract"); + if ("true".equals(abstractV)) { + String burl = src.getNamedChildValue("url"); + if (burl != null) { + boolean bok = false; + for (StructureDefinition sdb : context.fetchResourcesByType(StructureDefinition.class)) { + if (burl.equals(sdb.getBaseDefinition())) { + bok = true; + } + } + warning(errors, "2024-12-31", IssueType.NOTFOUND, stack.getLiteralPath(), bok, I18nConstants.SD_DERIVATION_NO_CONCRETE, typeName); + } + } List differentials = src.getChildrenByName("differential"); List snapshots = src.getChildrenByName("snapshot"); boolean logical = "logical".equals(src.getNamedChildValue("kind", false)); @@ -684,33 +697,37 @@ public class StructureDefinitionValidator extends BaseValidator { boolean found = false; for (Element e : extensions) { if (ToolingExtensions.EXT_TYPE_PARAMETER.equals(e.getNamedChildValue("url"))) { - String ename = e.getExtensionValue("name").primitiveValue(); - if (name.equals(ename)) { - found = true; - String etype = e.getExtensionValue("type").primitiveValue(); - for (Extension ex : sd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_PARAMETER)) { - String tn = ex.getExtensionString("name"); - if (tn != null && tn.equals(etype)) { - etype = ex.getExtensionString("type"); - break; - } - } - StructureDefinition esd = context.fetchTypeDefinition(etype); - if (rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), esd != null, I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, tc, etype)) { - StructureDefinition t = esd; - while (t != null && t != psd) { - t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition()); - } - ok = rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_INVALID_REF, tc, etype, tsd.getVersionedUrl(), name, type) & ok; - if (t != null) { - if (!sd.getAbstract() && esd.getAbstract()) { - warning(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_ABSTRACT_WARNING, tc, etype, tsd.getVersionedUrl(), name, type); + if (!e.hasExtension("name")) { + rule(errors, "2024-12-31", IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, tc, "no name"); + } else { + String ename = e.getExtensionValue("name").primitiveValue(); + if (name.equals(ename)) { + found = true; + String etype = e.getExtensionValue("type").primitiveValue(); + for (Extension ex : sd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_PARAMETER)) { + String tn = ex.getExtensionString("name"); + if (tn != null && tn.equals(etype)) { + etype = ex.getExtensionString("type"); + break; } } - } else { - ok = false; - } - } + StructureDefinition esd = context.fetchTypeDefinition(etype); + if (rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), esd != null, I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, tc, etype)) { + StructureDefinition t = esd; + while (t != null && t != psd) { + t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition()); + } + ok = rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_INVALID_REF, tc, etype, tsd.getVersionedUrl(), name, type) & ok; + if (t != null) { + if (!sd.getAbstract() && esd.getAbstract()) { + warning(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_ABSTRACT_WARNING, tc, etype, tsd.getVersionedUrl(), name, type); + } + } + } else { + ok = false; + } + } + } } } ok = rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), found, I18nConstants.SD_TYPE_PARAM_NOT_SPECIFIED, tc, tsd.getVersionedUrl(), name, path) && ok; diff --git a/pom.xml b/pom.xml index 7cbfa7a84..ed5f5a589 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 2.17.0 32.0.1-jre 6.4.1 - 1.7.2 + 1.7.3-SNAPSHOT 2.17.0 5.9.2 1.8.2