From ca96bbfdc4c02606c741f4f1806a1d1d78a8d6ce Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 2 Nov 2022 20:55:35 +1100 Subject: [PATCH 1/5] fix bug where ElementModel paths are not populated --- .../java/org/hl7/fhir/r5/elementmodel/Element.java | 13 +++++++++++++ .../org/hl7/fhir/r5/elementmodel/JsonParser.java | 8 ++++++++ .../org/hl7/fhir/r5/elementmodel/TurtleParser.java | 4 ++++ .../org/hl7/fhir/r5/elementmodel/XmlParser.java | 6 ++++++ .../org/hl7/fhir/validation/ValidationEngine.java | 1 + .../validation/cli/services/ValidationService.java | 1 + 6 files changed, 33 insertions(+) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java index 40f9d988b..60c12c21d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java @@ -1238,5 +1238,18 @@ public class Element extends Base { } return "e:"+e+",w:"+w+",h:"+h; } + + public void populatePaths(String path) { + if (path == null) { + path = fhirType(); + } + setPath(path); + if (children != null) { + for (Element n : children) { + n.populatePaths(path+"."+n.getName()); + } + } + + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java index 83f05d481..ad66a5aa3 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java @@ -636,6 +636,10 @@ public class JsonParser extends ParserBase { @Override public void compose(Element e, OutputStream stream, OutputStyle style, String identity) throws FHIRException, IOException { + if (e.getPath() == null) { + e.populatePaths(null); + } + OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); if (style == OutputStyle.CANONICAL) json = new JsonCreatorCanonical(osw); @@ -654,6 +658,10 @@ public class JsonParser extends ParserBase { } public void compose(Element e, JsonCreator json) throws Exception { + if (e.getPath() == null) { + e.populatePaths(null); + } + this.json = json; json.beginObject(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/TurtleParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/TurtleParser.java index 351d9d72e..49002e6af 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/TurtleParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/TurtleParser.java @@ -315,6 +315,10 @@ public class TurtleParser extends ParserBase { public void compose(Element e, Turtle ttl, String base) throws FHIRException { + if (e.getPath() == null) { + e.populatePaths(null); + } + ttl.prefix("fhir", FHIR_URI_BASE); ttl.prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#"); ttl.prefix("owl", "http://www.w3.org/2002/07/owl#"); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java index 9903e1ac1..2464a1e56 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java @@ -602,6 +602,9 @@ public class XmlParser extends ParserBase { xml.setSortAttributes(false); xml.setPretty(style == OutputStyle.PRETTY); xml.start(); + if (e.getPath() == null) { + e.populatePaths(null); + } String ns = e.getProperty().getXmlNamespace(); if (ns!=null && !"noNamespace".equals(ns)) { xml.setDefaultNamespace(ns); @@ -654,6 +657,9 @@ public class XmlParser extends ParserBase { } public void compose(Element e, IXMLWriter xml) throws Exception { + if (e.getPath() == null) { + e.populatePaths(null); + } xml.start(); xml.setDefaultNamespace(e.getProperty().getXmlNamespace()); if (schemaPath != null) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index b90748cd6..8f0bdfc24 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -618,6 +618,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP if (map == null) throw new Error("Unable to find map " + mapUri + " (Known Maps = " + context.listMapUrls() + ")"); org.hl7.fhir.r5.elementmodel.Element resource = getTargetResourceFromStructureMap(map); scu.transform(null, src, map, resource); + resource.populatePaths(null); return resource; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index 984440296..9387d6770 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -385,6 +385,7 @@ public class ValidationService { for (String src : cliContext.getIgs()) { igLoader.loadIg(validator.getIgs(), validator.getBinaries(), src, cliContext.isRecursive()); } + System.out.println(" Package Summary: "+validator.getContext().loadedPackageSummary()); System.out.print(" Get set... "); validator.setQuestionnaireMode(cliContext.getQuestionnaireMode()); validator.setLevel(cliContext.getLevel()); From c8bf2aa4e53bbe671b473cff2e7b8ecf4c944ef5 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 2 Nov 2022 20:56:17 +1100 Subject: [PATCH 2/5] fix issue with contained resources not rendering --- .../r5/renderers/ProfileDrivenRenderer.java | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java index ef8f9c6a0..9bea86809 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java @@ -68,6 +68,7 @@ import org.hl7.fhir.r5.renderers.utils.DirectWrappers; import org.hl7.fhir.r5.renderers.utils.DirectWrappers.BaseWrapperDirect; import org.hl7.fhir.r5.renderers.utils.DirectWrappers.PropertyWrapperDirect; import org.hl7.fhir.r5.renderers.utils.DirectWrappers.ResourceWrapperDirect; +import org.hl7.fhir.r5.renderers.utils.ElementWrappers; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; @@ -103,7 +104,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { boolean idDone = false; XhtmlNode p = x.para(); if (context.isAddGeneratedNarrativeHeader()) { - p.b().tx("Generated Narrative: "+r.fhirType()); + p.b().tx("Generated Narrative: "+r.fhirType()+(context.isContained() ? " #"+r.getId() : "")); if (!Utilities.noString(r.getId())) { p.an(r.getId()); } @@ -756,35 +757,50 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if (children.isEmpty()) { renderLeaf(res, e, defn, x, x, false, showCodeDetails, readDisplayHints(defn), path, indent); } else { - for (PropertyWrapper p : splitExtensions(profile, e.children())) { - if (p.hasValues()) { - ElementDefinition child = getElementDefinition(children, path+"."+p.getName(), p); - if (child == null) { - child = p.getElementDefinition(); - } - if (child != null) { - if (!child.getBase().hasPath() || !child.getBase().getPath().startsWith("Resource.")) { - generateElementByProfile(res, profile, allElements, x, path, showCodeDetails, indent, p, child); - } - } + List pl = splitExtensions(profile, e.children()); + for (PropertyWrapper p : pl) { + generateForProperty(res, profile, allElements, children, x, path, showCodeDetails, indent, false, p); + } + for (PropertyWrapper p : pl) { + generateForProperty(res, profile, allElements, children, x, path, showCodeDetails, indent, true, p); + } + } + } + + private void generateForProperty(ResourceWrapper res, StructureDefinition profile, + List allElements, List children, XhtmlNode x, String path, + boolean showCodeDetails, int indent, boolean round2, PropertyWrapper p) + throws UnsupportedEncodingException, IOException, EOperationOutcome { + if (p.hasValues()) { + ElementDefinition child = getElementDefinition(children, path+"."+p.getName(), p); + if (child == null) { + child = p.getElementDefinition(); + } + if (child != null) { + if (!child.getBase().hasPath() || !child.getBase().getPath().startsWith("Resource.")) { + generateElementByProfile(res, profile, allElements, x, path, showCodeDetails, indent, p, child, round2); } } } } public void generateElementByProfile(ResourceWrapper res, StructureDefinition profile, List allElements, XhtmlNode x, String path, - boolean showCodeDetails, int indent, PropertyWrapper p, ElementDefinition child) throws UnsupportedEncodingException, IOException, EOperationOutcome { + boolean showCodeDetails, int indent, PropertyWrapper p, ElementDefinition child, boolean round2) throws UnsupportedEncodingException, IOException, EOperationOutcome { Map displayHints = readDisplayHints(child); if ("DomainResource.contained".equals(child.getBase().getPath())) { -// for (BaseWrapper v : p.getValues()) { -// x.hr(); -// RenderingContext ctxt = context.clone(); -// ctxt.setContained(true); -// ResourceRenderer rnd = RendererFactory.factory(v.fhirType(), ctxt); -// ResourceWrapper rw = new ElementWrappers.ResourceWrapperMetaElement(ctxt, (org.hl7.fhir.r5.elementmodel.Element) v.getBase()); -// rnd.render(x.blockquote(), rw); -// } - } else if (!exemptFromRendering(child)) { + if (round2) { + for (BaseWrapper v : p.getValues()) { + if (!RendererFactory.hasSpecificRenderer(v.fhirType())) { + x.hr(); + RenderingContext ctxt = context.copy(); + ctxt.setContained(true); + ResourceRenderer rnd = RendererFactory.factory(v.fhirType(), ctxt); + ResourceWrapper rw = new ElementWrappers.ResourceWrapperMetaElement(ctxt, (org.hl7.fhir.r5.elementmodel.Element) v.getBase()); + rnd.render(x.blockquote(), rw); + } + } + } + } else if (!round2 && !exemptFromRendering(child)) { if (isExtension(p)) { hasExtensions = true; } From 68a3b6c808f2802921739da662ee224a8e15601d Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 2 Nov 2022 20:56:40 +1100 Subject: [PATCH 3/5] process markdown relative references --- .../hl7/fhir/r5/renderers/DataRenderer.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) 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 df959a022..e0b7b1391 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 @@ -15,6 +15,7 @@ import java.time.format.FormatStyle; import java.time.format.SignStyle; import java.util.Currency; import java.util.List; +import java.util.Set; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; @@ -97,6 +98,45 @@ public class DataRenderer extends Renderer { // -- 2. Markdown support ------------------------------------------------------- + public static String processRelativeUrls(String markdown, String path) { + if (markdown == null) { + return ""; + } + if (!Utilities.isAbsoluteUrl(path)) { + return markdown; + } + String basePath = path.contains("/") ? path.substring(0, path.lastIndexOf("/")+1) : path+"/"; + StringBuilder b = new StringBuilder(); + int i = 0; + while (i < markdown.length()) { + if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { + int j = i + 2; + while (j < markdown.length() && markdown.charAt(j) != ')') + j++; + if (j < markdown.length()) { + String url = markdown.substring(i+2, j); + if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { + // it's relative - so it's relative to the base URL + b.append("]("); + b.append(basePath); + } else { + b.append("]("); + } + i = i + 1; + } else + b.append(markdown.charAt(i)); + } else { + b.append(markdown.charAt(i)); + } + i++; + } + return b.toString(); + } + + protected void addMarkdown(XhtmlNode x, String text, String path) throws FHIRFormatError, IOException, DefinitionException { + addMarkdown(x, processRelativeUrls(text, path)); + } + protected void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException { if (text != null) { // 1. custom FHIR extensions From 16e21c5bbebb574464f739496693b19eef232823 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 2 Nov 2022 20:57:05 +1100 Subject: [PATCH 4/5] fix bug in simple http client when no accept header is set --- .../main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java | 2 +- .../main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java index ad76e514a..c5e8c5198 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java @@ -769,7 +769,7 @@ public class ValueSetRenderer extends TerminologyRenderer { td = tr.td(); if (cs != null) { String defn = CodeSystemUtilities.getCodeDefinition(cs, c.getCode()); - addMarkdown(td, defn); + addMarkdown(td, defn, cs.getUserString("path")); } } for (String n : Utilities.sorted(properties.keySet())) { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java index 3b4a5b6e5..aba7870da 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java @@ -139,7 +139,9 @@ public class SimpleHTTPClient { u = new URL(url); c = (HttpURLConnection) u.openConnection(); c.setRequestMethod("GET"); - c.setRequestProperty("Accept", accept); + if (accept != null) { + c.setRequestProperty("Accept", accept); + } setHeaders(c); c.setInstanceFollowRedirects(false); if (trustAll && url.startsWith("https://")) { From 21837ea6d5c89b93c364309f03555e8497fab375 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 2 Nov 2022 20:57:36 +1100 Subject: [PATCH 5/5] fix min/max decimal checks --- .../validation/instance/InstanceValidator.java | 18 +++++++++++++----- .../org.hl7.fhir.validation/4.0.1/snomed.cache | 11 +++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 069635dcc..03860f556 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -2410,12 +2410,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false); if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID, e.primitiveValue())) { warning(errors, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue()); - try { + try { Decimal v = new Decimal(e.getValue()); - ok = rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueDecimalType() || - !context.getMaxValueDecimalType().hasValue() || checkDecimalMaxValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, (context.hasMaxValueDecimalType() ? context.getMaxValueDecimalType() : "")) && ok; - ok = rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || - !context.getMinValueIntegerType().hasValue() || checkDecimalMinValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : "")) && ok; + if (context.hasMaxValueDecimalType() && context.getMaxValueDecimalType().hasValue()) { + ok = rule(errors, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMaxValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, context.getMaxValueDecimalType()) && ok; + } else if (context.hasMaxValueIntegerType() && context.getMaxValueIntegerType().hasValue()) { + // users can also provide a max integer type. It's not clear whether that's actually valid, but we'll check for it anyway + ok = rule(errors, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMaxValue(v, new BigDecimal(context.getMaxValueIntegerType().getValue())), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, context.getMaxValueIntegerType()) && ok; + } + + if (context.hasMinValueDecimalType() && context.getMaxValueDecimalType().hasValue()) { + ok = rule(errors, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMaxValueDecimalType()) && ok; + } else if (context.hasMinValueIntegerType() && context.getMaxValueIntegerType().hasValue()) { + ok = rule(errors, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, new BigDecimal(context.getMaxValueIntegerType().getValue())), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMaxValueIntegerType()) && ok; + } } catch (Exception ex) { // should never happen? } diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache index 24182aed8..02c5cb8ed 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache @@ -2067,3 +2067,14 @@ v: { "system" : "http://snomed.info/sct" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "324689003", + "display" : "Nystatin 100000 unit/mL oral suspension" +}, "valueSet" :null, "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "versionFlexible":"false"}#### +v: { + "display" : "Product containing precisely nystatin 100000 unit/1 milliliter conventional release oral suspension (clinical drug)", + "code" : "324689003", + "system" : "http://snomed.info/sct" +} +-------------------------------------------------------------------------------------