From 8c438031833bce6d1ea466b2dbfb1a5feeb74a70 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 2 Sep 2020 08:09:36 +1000 Subject: [PATCH 01/21] Fix up conversion for date problems --- .../org/hl7/fhir/convertors/VersionConvertor_10_40.java | 7 +++++++ .../fhir/convertors/conv10_40/MedicationRequest10_40.java | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_10_40.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_10_40.java index 431207402..c9550402d 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_10_40.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_10_40.java @@ -65,6 +65,7 @@ import org.hl7.fhir.convertors.conv10_40.ValueSet10_40; import org.hl7.fhir.dstu2.model.CodeableConcept; import org.hl7.fhir.dstu2.model.Parameters; import org.hl7.fhir.dstu2.model.Parameters.ParametersParameterComponent; +import org.hl7.fhir.dstu2.model.PositiveIntType; import org.hl7.fhir.dstu2.model.Reference; import org.hl7.fhir.dstu2.utils.ToolingExtensions; import org.hl7.fhir.exceptions.FHIRException; @@ -3423,4 +3424,10 @@ public class VersionConvertor_10_40 { public static org.hl7.fhir.dstu2.model.Resource convertResource(org.hl7.fhir.r4.model.Resource src) throws FHIRException { return convertResource(src, null); } + + public static UnsignedIntType convertUnsignedIntToPositive(PositiveIntType src) { + org.hl7.fhir.r4.model.UnsignedIntType tgt = src.hasValue() ? new org.hl7.fhir.r4.model.UnsignedIntType(src.getValue()) : new org.hl7.fhir.r4.model.UnsignedIntType(); + copyElement(src, tgt); + return tgt; + } } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/MedicationRequest10_40.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/MedicationRequest10_40.java index 25e745d33..e0e3bbed0 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/MedicationRequest10_40.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/MedicationRequest10_40.java @@ -15,7 +15,7 @@ public class MedicationRequest10_40 { tgt.setIntent(org.hl7.fhir.r4.model.MedicationRequest.MedicationRequestIntent.ORDER); for (org.hl7.fhir.dstu2.model.Identifier identifier : src.getIdentifier()) tgt.addIdentifier(VersionConvertor_10_40.convertIdentifier(identifier)); if (src.hasDateWritten()) - tgt.setAuthoredOn(src.getDateWritten()); + tgt.setAuthoredOnElement(VersionConvertor_10_40.convertDateTime(src.getDateWrittenElement())); if (src.hasStatus()) tgt.setStatus(org.hl7.fhir.r4.model.MedicationRequest.MedicationRequestStatus.fromCode(src.getStatus().toCode())); if (src.hasPatient()) @@ -48,7 +48,7 @@ public class MedicationRequest10_40 { return null; org.hl7.fhir.r4.model.Dosage tgt = new org.hl7.fhir.r4.model.Dosage(); if (src.hasText()) - tgt.setText(src.getText()); + tgt.setTextElement(VersionConvertor_10_40.convertString(src.getTextElement())); if (src.hasAdditionalInstructions()) tgt.addAdditionalInstruction(VersionConvertor_10_40.convertCodeableConcept(src.getAdditionalInstructions())); if (src.hasTiming()) @@ -79,7 +79,7 @@ public class MedicationRequest10_40 { if (src.hasValidityPeriod()) tgt.setValidityPeriod(VersionConvertor_10_40.convertPeriod(src.getValidityPeriod())); if (src.hasNumberOfRepeatsAllowed()) - tgt.setNumberOfRepeatsAllowed(src.getNumberOfRepeatsAllowed()); + tgt.setNumberOfRepeatsAllowedElement(VersionConvertor_10_40.convertUnsignedIntToPositive(src.getNumberOfRepeatsAllowedElement())); if (src.hasQuantity()) tgt.setQuantity(VersionConvertor_10_40.convertSimpleQuantity(src.getQuantity())); if (src.hasExpectedSupplyDuration()) From e1f53eec47ef191d65c970388c27f47e976251a6 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 2 Sep 2020 16:34:06 +1000 Subject: [PATCH 02/21] Mark packages as unsuitable for publication --- .../fhir/r5/utils/NPMPackageGenerator.java | 49 +++++++++++++------ .../hl7/fhir/utilities/cache/NpmPackage.java | 4 ++ 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java index 22a5d0c54..0a46a7fcf 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java @@ -104,17 +104,17 @@ public class NPMPackageGenerator { private NpmPackageIndexBuilder indexer; - public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, Date date) throws FHIRException, IOException { + public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, Date date, boolean notForPublication) throws FHIRException, IOException { super(); this.destFile = destFile; start(); List fhirVersion = new ArrayList<>(); for (Enumeration v : ig.getFhirVersion()) fhirVersion.add(v.asStringValue()); - buildPackageJson(canonical, kind, url, date, ig, fhirVersion); + buildPackageJson(canonical, kind, url, date, ig, fhirVersion, notForPublication); } - public static NPMPackageGenerator subset(NPMPackageGenerator master, String destFile, String id, String name, Date date) throws FHIRException, IOException { + public static NPMPackageGenerator subset(NPMPackageGenerator master, String destFile, String id, String name, Date date, boolean notForPublication) throws FHIRException, IOException { JsonObject p = master.packageJ.deepCopy(); p.remove("name"); p.addProperty("name", id); @@ -122,24 +122,30 @@ public class NPMPackageGenerator { p.addProperty("type", PackageType.SUBSET.getCode()); p.remove("title"); p.addProperty("title", name); + if (notForPublication) { + p.addProperty("notForPublication", true); + } - return new NPMPackageGenerator(destFile, p, date); + return new NPMPackageGenerator(destFile, p, date, notForPublication); } - public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, Date date, List fhirVersion) throws FHIRException, IOException { + public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, Date date, List fhirVersion, boolean notForPublication) throws FHIRException, IOException { super(); this.destFile = destFile; start(); - buildPackageJson(canonical, kind, url, date, ig, fhirVersion); + buildPackageJson(canonical, kind, url, date, ig, fhirVersion, notForPublication); } - public NPMPackageGenerator(String destFile, JsonObject npm, Date date) throws FHIRException, IOException { + public NPMPackageGenerator(String destFile, JsonObject npm, Date date, boolean notForPublication) throws FHIRException, IOException { super(); String dt = new SimpleDateFormat("yyyyMMddHHmmss").format(date); packageJ = npm; packageManifest = new JsonObject(); packageManifest.addProperty("version", npm.get("version").getAsString()); packageManifest.addProperty("date", dt); + if (notForPublication) { + packageManifest.addProperty("notForPublication", true); + } npm.addProperty("date", dt); packageManifest.addProperty("name", npm.get("name").getAsString()); this.destFile = destFile; @@ -152,19 +158,23 @@ public class NPMPackageGenerator { } } - private void buildPackageJson(String canonical, PackageType kind, String web, Date date, ImplementationGuide ig, List fhirVersion) throws FHIRException, IOException { + private void buildPackageJson(String canonical, PackageType kind, String web, Date date, ImplementationGuide ig, List fhirVersion, boolean notForPublication) throws FHIRException, IOException { String dtHuman = new SimpleDateFormat("EEE, MMM d, yyyy HH:mmZ", new Locale("en", "US")).format(date); String dt = new SimpleDateFormat("yyyyMMddHHmmss").format(date); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); - if (!ig.hasPackageId()) + if (!ig.hasPackageId()) { b.append("packageId"); - if (!ig.hasVersion()) + } + if (!ig.hasVersion()) { b.append("version"); - if (!ig.hasFhirVersion()) + } + if (!ig.hasFhirVersion()) { b.append("fhirVersion"); - if (!ig.hasLicense()) + } + if (!ig.hasLicense()) { b.append("license"); + } for (ImplementationGuideDependsOnComponent d : ig.getDependsOn()) { if (!d.hasVersion()) { b.append("dependsOn.version("+d.getUri()+")"); @@ -177,14 +187,20 @@ public class NPMPackageGenerator { npm.addProperty("tools-version", ToolsVersion.TOOLS_VERSION); npm.addProperty("type", kind.getCode()); npm.addProperty("date", dt); - if (ig.hasLicense()) + if (ig.hasLicense()) { npm.addProperty("license", ig.getLicense().toCode()); + } npm.addProperty("canonical", canonical); + if (notForPublication) { + npm.addProperty("notForPublication", true); + } npm.addProperty("url", web); - if (ig.hasTitle()) + if (ig.hasTitle()) { npm.addProperty("title", ig.getTitle()); - if (ig.hasDescription()) + } + if (ig.hasDescription()) { npm.addProperty("description", ig.getDescription()+ " (built "+dtHuman+timezone()+")"); + } JsonArray vl = new JsonArray(); npm.add("fhirVersions", vl); @@ -205,8 +221,9 @@ public class NPMPackageGenerator { dep.addProperty(d.getPackageId(), d.getVersion()); } } - if (ig.hasPublisher()) + if (ig.hasPublisher()) { npm.addProperty("author", ig.getPublisher()); + } JsonArray m = new JsonArray(); for (ContactDetail t : ig.getContact()) { String email = email(t.getTelecom()); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java index 75d298867..b87b60818 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java @@ -1091,6 +1091,10 @@ public class NpmPackage { } return true; } + + public boolean isNotForPublication() { + return JSONUtil.bool(npm, "notForPublication"); + } } \ No newline at end of file From fc7f71c1f60101f1f0b031bad72a68c7a5fa07ec Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 2 Sep 2020 16:34:34 +1000 Subject: [PATCH 03/21] rework rendering tests & fix bug in Parameters renderer --- .../fhir/r5/renderers/ParametersRenderer.java | 10 +++- .../fhir/r5/renderers/RendererFactory.java | 3 + .../fhir/r5/renderers/utils/BaseWrappers.java | 6 +- .../r5/test/NarrativeGenerationTests.java | 60 +++++++++++++++---- .../fhir/r5/test/ResourceRoundTripTests.java | 3 + 5 files changed, 65 insertions(+), 17 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java index fc8e6436b..d6fcf528e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java @@ -31,13 +31,19 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class ParametersRenderer extends ResourceRenderer { + public ParametersRenderer(RenderingContext context) { + super(context); + } + public ParametersRenderer(RenderingContext context, ResourceContext rcontext) { super(context, rcontext); } - @Override public boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { + x.h2().tx("Parameters"); + XhtmlNode tbl = x.table("grid"); + params(tbl, ((Parameters) r).getParameter(), 0); return false; } @@ -87,7 +93,7 @@ public class ParametersRenderer extends ResourceRenderer { } } else if (p.has("part")) { tr.td(); - PropertyWrapper pw = getProperty(p, "parameter"); + PropertyWrapper pw = getProperty(p, "part"); paramsW(tbl, pw.getValues(), 1); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java index 9fb41cc12..5935729b4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java @@ -72,6 +72,9 @@ public class RendererFactory { if ("OperationOutcome".equals(resourceName)) { return new OperationOutcomeRenderer(context); } + if ("Parameters".equals(resourceName)) { + return new ParametersRenderer(context); + } return new ProfileDrivenRenderer(context); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java index b811ac7fe..005da8090 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java @@ -87,7 +87,7 @@ public class BaseWrappers { @Override public boolean has(String name) { for (PropertyWrapper p : children()) { - if (p.getName().equals(name)) { + if (p.getName().equals(name) || p.getName().equals(name+"[x]") ) { return p.hasValues(); } } @@ -97,7 +97,7 @@ public class BaseWrappers { @Override public Base get(String name) throws UnsupportedEncodingException, FHIRException, IOException { for (PropertyWrapper p : children()) { - if (p.getName().equals(name)) { + if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { if (p.hasValues()) { return p.getValues().get(0).getBase(); } else { @@ -111,7 +111,7 @@ public class BaseWrappers { @Override public List children(String name) throws UnsupportedEncodingException, FHIRException, IOException { for (PropertyWrapper p : children()) { - if (p.getName().equals(name)) { + if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { List res = new ArrayList<>(); for (BaseWrapper b : p.getValues()) { res.add(b); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java index c320f912f..9011cd929 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java @@ -11,22 +11,32 @@ import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.SystemUtils; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.elementmodel.Manager; +import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.XmlParser; +import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Questionnaire; +import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.RendererFactory; import org.hl7.fhir.r5.renderers.ResourceRenderer; +import org.hl7.fhir.r5.renderers.utils.ElementWrappers; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.RenderingContext.ITypeParser; import org.hl7.fhir.r5.renderers.utils.RenderingContext.QuestionnaireRendererMode; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; +import org.hl7.fhir.r5.test.NarrativeGenerationTests.TestTypeParser; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.utilities.TerminologyServiceOptions; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.xhtml.XhtmlComposer; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; +import org.hl7.fhir.utilities.xhtml.XhtmlParser; import org.hl7.fhir.utilities.xml.XMLUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -40,6 +50,15 @@ import org.xml.sax.SAXException; public class NarrativeGenerationTests { + public class TestTypeParser implements ITypeParser { + + @Override + public Base parseType(String xml, String type) throws FHIRFormatError, IOException, FHIRException { + return new org.hl7.fhir.r5.formats.XmlParser().parseType(xml, type); + } + + } + public static final String WINDOWS = "WINDOWS"; private static final String HEADER = ""+ @@ -57,11 +76,13 @@ public class NarrativeGenerationTests { public static class TestDetails { private String id; private boolean header; + private boolean meta; public TestDetails(Element test) { super(); id = test.getAttribute("id"); header = "true".equals(test.getAttribute("header")); + meta = "true".equals(test.getAttribute("meta")); } public String getId() { @@ -70,6 +91,10 @@ public class NarrativeGenerationTests { public boolean isHeader() { return header; + } + + public boolean isMeta() { + return meta; } } @@ -107,19 +132,30 @@ public class NarrativeGenerationTests { rc.setHeader(test.isHeader()); rc.setDefinitionsTarget("test.html"); rc.setTerminologyServiceOptions(TerminologyServiceOptions.defaults()); - IOUtils.copy(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-expected.xml"), new FileOutputStream(TestingUtilities.tempFile("narrative", test.getId() + "-expected.xml"))); - DomainResource source; - if (TestingUtilities.findTestResource("r5", "narrative", test.getId() + "-input.json")) { - source = (DomainResource) new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-input.json")); + rc.setParser(new TestTypeParser()); + Resource source; + if (TestingUtilities.findTestResource("r5", "narrative", test.getId() + ".json")) { + source = (Resource) new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".json")); } else { - source = (DomainResource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-input.xml")); + source = (Resource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml")); + } + + XhtmlNode x = RendererFactory.factory(source, rc).build(source); + String target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".html")); + String output = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER; + TextFile.stringToFile(target, TestingUtilities.tempFile("narrative", test.getId() + ".target.html")); + TextFile.stringToFile(output, TestingUtilities.tempFile("narrative", test.getId() + ".output.html")); + Assertions.assertTrue(output.equals(target), "Output does not match expected"); + + if (test.isMeta()) { + org.hl7.fhir.r5.elementmodel.Element e = Manager.parse(context, TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml"), FhirFormat.XML); + x = RendererFactory.factory(source, rc).render(new ElementWrappers.ResourceWrapperMetaElement(rc, e)); + + target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html")); + output = HEADER+new XhtmlComposer(true).compose(x)+FOOTER; + TextFile.stringToFile(output, TestingUtilities.tempFile("narrative", test.getId() + "-meta.output.html")); + Assertions.assertTrue(output.equals(target), "Output does not match expected (meta)"); } - DomainResource target = (DomainResource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-expected.xml")); - RendererFactory.factory(source, rc).render(source); - new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilities.tempFile("narrative", test.getId() + "-actual.xml")), source); - source = (DomainResource) new XmlParser().parse(new FileInputStream(TestingUtilities.tempFile("narrative", test.getId() + "-actual.xml"))); - String html = HEADER+new XhtmlComposer(true).compose(source.getText().getDiv())+FOOTER; - TextFile.stringToFile(html, TestingUtilities.tempFile("narrative", test.getId() + ".html")); - Assertions.assertTrue(source.equalsDeep(target), "Output does not match expected"); } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java index 6406fb930..ba462e423 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java @@ -13,6 +13,7 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.DateTimeType; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.RendererFactory; @@ -20,6 +21,7 @@ import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.EOperationOutcome; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class ResourceRoundTripTests { @@ -52,4 +54,5 @@ public class ResourceRoundTripTests { if (result == null) throw new FHIRException("Bundle was null"); } + } \ No newline at end of file From 325c750814b540f2f5db118dcd5dde5b15834e48 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 2 Sep 2020 16:53:54 +1000 Subject: [PATCH 04/21] fix test case depedency --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b72385c45..611b9deb9 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 5.1.0 - 1.1.35 + 1.1.36-SNAPSHOT 5.6.2 3.0.0-M4 0.8.5 From fd6fe88528fff85efb4995d9132a1561fc25e164 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 4 Sep 2020 16:21:12 +1000 Subject: [PATCH 05/21] update VSACImporter to get value sets from VSAC directly --- .../fhir/convertors/misc/VSACImporter.java | 149 ++++++++++-------- 1 file changed, 85 insertions(+), 64 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java index 2e03d4430..dd791b3db 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java @@ -1,26 +1,29 @@ package org.hl7.fhir.convertors.misc; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r5.formats.JsonParser; -import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r4.utils.client.FHIRToolingClient; +import org.hl7.fhir.r4.formats.JsonParser; +import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.utilities.CSVReader; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.xml.XMLUtil; import org.w3c.dom.Element; import java.io.*; +import java.net.URISyntaxException; import java.text.ParseException; import java.util.ArrayList; import java.util.List; public class VSACImporter extends OIDBasedValueSetImporter { - public static void main(String[] args) throws FileNotFoundException, FHIRException, IOException, ParseException { + public static void main(String[] args) throws FileNotFoundException, FHIRException, IOException, ParseException, URISyntaxException { // new PhinVadsImporter().importValueSet(TextFile.fileToBytes("C:\\work\\org.hl7.fhir\\packages\\us.cdc.phinvads-source\\source\\PHVS_BirthDefectsLateralityatDiagnosis_HL7_V1.txt")); VSACImporter self = new VSACImporter(); - self.process(args[0], args[1]); + self.process(args[0], args[1], args[2], args[3]); } public VSACImporter() throws FileNotFoundException, FHIRException, IOException { @@ -28,69 +31,87 @@ public class VSACImporter extends OIDBasedValueSetImporter { init(); } - private void process(String source, String dest) { - for (File f : new File(source).listFiles()) { + private void process(String source, String dest, String username, String password) throws FHIRException, FileNotFoundException, IOException, URISyntaxException { + CSVReader csv = new CSVReader(new FileInputStream(source)); + csv.readHeaders(); + FHIRToolingClient client = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir", username, password); + int i = 0; + while (csv.line()) { + String oid = csv.cell("OID"); try { - System.out.println("Process " + f.getName()); - List vsl = importValueSet(TextFile.fileToBytes(f)); - for (ValueSet vs : vsl) { - if (vs.getId() != null) { - new JsonParser().compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + vs.getId() + ".json")), vs); - } + ValueSet vs = client.read(ValueSet.class, oid); + new JsonParser().compose(new FileOutputStream(Utilities.path(dest, "ValueSet-"+oid+".json")), vs); + i++; + if (i % 100 == 0) { + System.out.println(i); } } catch (Exception e) { - e.printStackTrace(); + System.out.println("Unable to fetch OID "+oid+": "+e.getMessage()); } } + System.out.println("Done. "+i+" ValueSets"); +// for (File f : new File(source).listFiles()) { +// try { +// System.out.println("Process " + f.getName()); +// List vsl = importValueSet(TextFile.fileToBytes(f)); +// for (ValueSet vs : vsl) { +// if (vs.getId() != null) { +// new JsonParser().compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + vs.getId() + ".json")), vs); +// } +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } } - private List importValueSet(byte[] source) throws Exception { - List res = new ArrayList(); - Element x = loadXml(new ByteArrayInputStream(source)).getDocumentElement(); - List vl = XMLUtil.getNamedChildren(x, "DescribedValueSet"); - for (Element v : vl) { - ValueSet vs = new ValueSet(); - vs.setId(v.getAttribute("ID")); - vs.setUrl("http://vsac.nlm.nih.gov/fhir/ValueSet/" + vs.getId()); - vs.getMeta().setSource("https://vsac.nlm.nih.gov/valueset/" + vs.getId() + "/expansion"); - vs.setVersion(v.getAttribute("version")); - vs.setTitle(v.getAttribute("displayName")); - vs.setName(Utilities.titleize(vs.getTitle()).replace(" ", "")); - Element d = XMLUtil.getNamedChild(v, "Purpose"); - if (d != null) { - vs.setDescription(d.getTextContent()); - } - Element s = XMLUtil.getNamedChild(v, "Status"); - if (s != null && "Active".equals(s.getTextContent())) { - vs.setStatus(PublicationStatus.ACTIVE); - } else { - vs.setStatus(PublicationStatus.DRAFT); - } - Element dt = XMLUtil.getNamedChild(v, "RevisionDate"); - if (dt != null) { - vs.getDateElement().setValueAsString(dt.getTextContent()); - } - - Element cl = XMLUtil.getNamedChild(v, "ConceptList"); - Element cc = XMLUtil.getFirstChild(cl); - - while (cc != null) { - String code = cc.getAttribute("code"); - String display = cc.getAttribute("displayName"); - String csoid = cc.getAttribute("codeSystem"); - String csver = cc.getAttribute("codeSystemVersion"); - String url = context.oid2Uri(csoid); - if (url == null) { - url = "urn:oid:" + csoid; - } - csver = fixVersionforSystem(url, csver); - ConceptSetComponent inc = getInclude(vs, url, csver); - inc.addConcept().setCode(code).setDisplay(display); - cc = XMLUtil.getNextSibling(cc); - } - - res.add(vs); - } - return res; - } +// private List importValueSet(byte[] source) throws Exception { +// List res = new ArrayList(); +// Element x = loadXml(new ByteArrayInputStream(source)).getDocumentElement(); +// List vl = XMLUtil.getNamedChildren(x, "DescribedValueSet"); +// for (Element v : vl) { +// ValueSet vs = new ValueSet(); +// vs.setId(v.getAttribute("ID")); +// vs.setUrl("http://cts.nlm.nih.gov/fhir/ValueSet/" + vs.getId()); +// vs.getMeta().setSource("https://vsac.nlm.nih.gov/valueset/" + vs.getId() + "/expansion"); +// vs.setVersion(v.getAttribute("version")); +// vs.setTitle(v.getAttribute("displayName")); +// vs.setName(Utilities.titleize(vs.getTitle()).replace(" ", "")); +// Element d = XMLUtil.getNamedChild(v, "Purpose"); +// if (d != null) { +// vs.setDescription(d.getTextContent()); +// } +// Element s = XMLUtil.getNamedChild(v, "Status"); +// if (s != null && "Active".equals(s.getTextContent())) { +// vs.setStatus(PublicationStatus.ACTIVE); +// } else { +// vs.setStatus(PublicationStatus.DRAFT); +// } +// Element dt = XMLUtil.getNamedChild(v, "RevisionDate"); +// if (dt != null) { +// vs.getDateElement().setValueAsString(dt.getTextContent()); +// } +// +// Element cl = XMLUtil.getNamedChild(v, "ConceptList"); +// Element cc = XMLUtil.getFirstChild(cl); +// +// while (cc != null) { +// String code = cc.getAttribute("code"); +// String display = cc.getAttribute("displayName"); +// String csoid = cc.getAttribute("codeSystem"); +// String csver = cc.getAttribute("codeSystemVersion"); +// String url = context.oid2Uri(csoid); +// if (url == null) { +// url = "urn:oid:" + csoid; +// } +// csver = fixVersionforSystem(url, csver); +// ConceptSetComponent inc = getInclude(vs, url, csver); +// inc.addConcept().setCode(code).setDisplay(display); +// cc = XMLUtil.getNextSibling(cc); +// } +// +// res.add(vs); +// } +// return res; +// } } From a964d4fcd543f6eb3351197f5089ad8ef187d838 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 4 Sep 2020 16:22:09 +1000 Subject: [PATCH 06/21] Fix up rendering of Extensions --- .../main/java/org/hl7/fhir/r5/model/Base.java | 3 +- .../hl7/fhir/r5/renderers/DataRenderer.java | 102 ++++++++++++ .../r5/renderers/ProfileDrivenRenderer.java | 154 ++++++++++++------ .../fhir/r5/renderers/utils/BaseWrappers.java | 2 + .../fhir/r5/renderers/utils/DOMWrappers.java | 5 + .../r5/renderers/utils/DirectWrappers.java | 15 ++ .../r5/renderers/utils/ElementWrappers.java | 7 +- .../hl7/fhir/utilities/xhtml/XhtmlNode.java | 6 + 8 files changed, 238 insertions(+), 56 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java index 13ceb36c3..c7f140781 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java @@ -171,8 +171,9 @@ private Map userData; List children = new ArrayList(); listChildren(children); for (Property c : children) - if (c.getName().equals(name)) + if (c.getName().equals(name) || c.getName().equals(name+"[x]")) { return c; + } return null; } 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 a51bb8a9e..b5dce979b 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 @@ -14,10 +14,16 @@ import org.hl7.fhir.r5.model.Annotation; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.BaseDateTimeType; import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.ContactPoint; +import org.hl7.fhir.r5.model.DataRequirement; +import org.hl7.fhir.r5.model.DataRequirement.DataRequirementCodeFilterComponent; +import org.hl7.fhir.r5.model.DataRequirement.DataRequirementDateFilterComponent; +import org.hl7.fhir.r5.model.DataRequirement.DataRequirementSortComponent; +import org.hl7.fhir.r5.model.DataRequirement.SortDirection; import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.DateTimeType; @@ -778,6 +784,102 @@ public class DataRenderer extends Renderer { x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); } + public void renderDataRequirement(XhtmlNode x, DataRequirement dr) { + XhtmlNode tbl = x.table("grid"); + XhtmlNode tr = tbl.tr(); + XhtmlNode td = tr.td().colspan("2"); + td.b().tx("Type"); + td.tx(": "); + StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.getType().toCode()); + if (sd != null && sd.hasUserData("path")) { + td.ah(sd.getUserString("path")).tx(dr.getType().toCode()); + } else { + td.tx(dr.getType().toCode()); + } + if (dr.hasProfile()) { + td.tx(" ("); + boolean first = true; + for (CanonicalType p : dr.getProfile()) { + if (first) first = false; else td.tx(" | "); + sd = context.getWorker().fetchResource(StructureDefinition.class, p.getValue()); + if (sd != null && sd.hasUserData("path")) { + td.ah(sd.getUserString("path")).tx(sd.present()); + } else { + td.tx(p.asStringValue()); + } + } + td.tx(")"); + } + if (dr.hasSubject()) { + tr = tbl.tr(); + td = tr.td().colspan("2"); + td.b().tx("Subject"); + if (dr.hasSubjectReference()) { + renderReference(td, dr.getSubjectReference()); + } else { + renderCodeableConcept(td, dr.getSubjectCodeableConcept()); + } + } + if (dr.hasCodeFilter() || dr.hasDateFilter()) { + tr = tbl.tr().backgroundColor("#efefef"); + tr.td().tx("Filter"); + tr.td().tx("Value"); + } + for (DataRequirementCodeFilterComponent cf : dr.getCodeFilter()) { + tr = tbl.tr(); + if (cf.hasPath()) { + tr.td().tx(cf.getPath()); + } else { + tr.td().tx("Search on " +cf.getSearchParam()); + } + if (cf.hasValueSet()) { + td = tr.td(); + td.tx("In ValueSet "); + render(td, cf.getValueSetElement()); + } else { + boolean first = true; + td = tr.td(); + td.tx("One of these codes: "); + for (Coding c : cf.getCode()) { + if (first) first = false; else td.tx(", "); + render(td, c); + } + } + } + for (DataRequirementDateFilterComponent cf : dr.getDateFilter()) { + tr = tbl.tr(); + if (cf.hasPath()) { + tr.td().tx(cf.getPath()); + } else { + tr.td().tx("Search on " +cf.getSearchParam()); + } + render(tr.td(), cf.getValue()); + } + if (dr.hasSort() || dr.hasLimit()) { + tr = tbl.tr(); + td = tr.td().colspan("2"); + if (dr.hasLimit()) { + td.b().tx("Limit"); + td.tx(": "); + td.tx(dr.getLimit()); + if (dr.hasSort()) { + td.tx(", "); + } + } + if (dr.hasSort()) { + td.b().tx("Sort"); + td.tx(": "); + boolean first = true; + for (DataRequirementSortComponent p : dr.getSort()) { + if (first) first = false; else td.tx(" | "); + td.tx(p.getDirection() == SortDirection.ASCENDING ? "+" : "-"); + td.tx(p.getPath()); + } + } + } + } + + private String displayTiming(Timing s) throws FHIRException { CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); if (s.hasCode()) 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 9b5841bb0..88870997d 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 @@ -27,6 +27,7 @@ import org.hl7.fhir.r5.model.CodeableReference; import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.ContactDetail; import org.hl7.fhir.r5.model.ContactPoint; +import org.hl7.fhir.r5.model.DataRequirement; import org.hl7.fhir.r5.model.DateTimeType; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Dosage; @@ -82,6 +83,7 @@ import org.w3c.dom.Element; public class ProfileDrivenRenderer extends ResourceRenderer { private Set containedIds = new HashSet<>(); + private boolean hasExtensions; public ProfileDrivenRenderer(RenderingContext context, ResourceContext rcontext) { super(context, rcontext); @@ -108,12 +110,13 @@ public class ProfileDrivenRenderer extends ResourceRenderer { System.out.println("hah!"); } containedIds.clear(); + hasExtensions = false; generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), false, 0); } catch (Exception e) { e.printStackTrace(); x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); } - return false; + return hasExtensions; } @Override @@ -378,6 +381,9 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } } else if (e instanceof Resource) { return; + } else if (e instanceof DataRequirement) { + DataRequirement p = (DataRequirement) e; + renderDataRequirement(x, p); } else if (e instanceof ElementDefinition) { x.tx("todo-bundle"); } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) { @@ -386,7 +392,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet, and no structure found"); else generateByProfile(res, sd, ew, sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep(), - getChildrenForPath(sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep().getPath()), x, path, showCodeDetails, indent + 1); + getChildrenForPath(sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep().getPath()), x, e.fhirType(), showCodeDetails, indent + 1); } } @@ -612,65 +618,98 @@ public class ProfileDrivenRenderer extends ResourceRenderer { 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) { - Map displayHints = readDisplayHints(child); - if ("DomainResource.contained".equals(child.getBase().getPath())) { + generateElementByProfile(res, profile, allElements, x, path, showCodeDetails, indent, p, child); + } + } + } + } + } + + 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 { + Map displayHints = readDisplayHints(child); + if ("DomainResource.contained".equals(child.getBase().getPath())) { // if (p.getValues().size() > 0 && child != null) { // for (BaseWrapper v : p.getValues()) { // x.an(v.get("id").primitiveValue()); // } // } - } else if (!exemptFromRendering(child)) { - List grandChildren = getChildrenForPath(allElements, path+"."+p.getName()); - filterGrandChildren(grandChildren, path+"."+p.getName(), p); - if (p.getValues().size() > 0) { - if (isPrimitive(child)) { - XhtmlNode para = x.para(); - String name = p.getName(); - if (name.endsWith("[x]")) - name = name.substring(0, name.length() - 3); - if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { - para.b().addText(name); - para.tx(": "); - if (renderAsList(child) && p.getValues().size() > 1) { - XhtmlNode list = x.ul(); - for (BaseWrapper v : p.getValues()) - renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent); - } else { - boolean first = true; - for (BaseWrapper v : p.getValues()) { - if (first) - first = false; - else - para.tx(", "); - renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); - } - } - } - } else if (canDoTable(path, p, grandChildren)) { - x.addTag(getHeader()).addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); - XhtmlNode tbl = x.table( "grid"); - XhtmlNode tr = tbl.tr(); - tr.td().tx("-"); // work around problem with empty table rows - addColumnHeadings(tr, grandChildren); - for (BaseWrapper v : p.getValues()) { - if (v != null) { - tr = tbl.tr(); - tr.td().tx("*"); // work around problem with empty table rows - addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent); - } - } - } else { - for (BaseWrapper v : p.getValues()) { - if (v != null) { - XhtmlNode bq = x.addTag("blockquote"); - bq.para().b().addText(p.getName()); - generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails, indent+1); - } - } + } else if (!exemptFromRendering(child)) { + if (isExtension(p)) { + hasExtensions = true; + } + List grandChildren = getChildrenForPath(allElements, path+"."+p.getName()); + filterGrandChildren(grandChildren, path+"."+p.getName(), p); + if (p.getValues().size() > 0) { + if (isPrimitive(child)) { + XhtmlNode para = x.para(); + String name = p.getName(); + if (name.endsWith("[x]")) + name = name.substring(0, name.length() - 3); + if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { + para.b().addText(name); + para.tx(": "); + if (renderAsList(child) && p.getValues().size() > 1) { + XhtmlNode list = x.ul(); + for (BaseWrapper v : p.getValues()) + renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent); + } else { + boolean first = true; + for (BaseWrapper v : p.getValues()) { + if (first) + first = false; + else + para.tx(", "); + renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); + } + } + } + } else if (canDoTable(path, p, grandChildren)) { + x.addTag(getHeader()).addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); + XhtmlNode tbl = x.table( "grid"); + XhtmlNode tr = tbl.tr(); + tr.td().tx("-"); // work around problem with empty table rows + addColumnHeadings(tr, grandChildren); + for (BaseWrapper v : p.getValues()) { + if (v != null) { + tr = tbl.tr(); + tr.td().tx("*"); // work around problem with empty table rows + addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent); + } + } + } else if (isExtension(p)) { + for (BaseWrapper v : p.getValues()) { + if (v != null) { + PropertyWrapper vp = v.getChildByName("value"); + PropertyWrapper ev = v.getChildByName("extension"); + if (vp.hasValues()) { + BaseWrapper vv = vp.value(); + XhtmlNode para = x.para(); + para.b().addText(p.getStructure().present()); + para.tx(": "); + renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); + } else if (ev.hasValues()) { + XhtmlNode bq = x.addTag("blockquote"); + bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName()); + for (BaseWrapper vv : ev.getValues()) { + StructureDefinition ex = context.getWorker().fetchTypeDefinition("Extension"); + List children = getChildrenForPath(ex.getSnapshot().getElement(), "Extension"); + generateByProfile(res, ex, vv, allElements, child, children, bq, "Extension", showCodeDetails, indent+1); } } } + } + } else { + for (BaseWrapper v : p.getValues()) { + if (v != null) { + XhtmlNode bq = x.addTag("blockquote"); + bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName()); + generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails, indent+1); + } } } } @@ -699,6 +738,9 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } private boolean canDoTable(String path, PropertyWrapper p, List grandChildren) { + if (isExtension(p)) { + return false; + } for (ElementDefinition e : grandChildren) { List values = getValues(path, p, e); if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e)) @@ -707,6 +749,10 @@ public class ProfileDrivenRenderer extends ResourceRenderer { return true; } + public boolean isExtension(PropertyWrapper p) { + return p.getName().contains("extension["); + } + private boolean canCollapse(ElementDefinition e) { // we can collapse any data type @@ -792,10 +838,10 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) throw new DefinitionException("unknown extension "+url); // System.out.println("unknown extension "+url); - pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex)); + pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), ed.getSnapshot().getElementFirstRep()); } else { ElementDefinition def = ed.getSnapshot().getElement().get(0); - pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex)); + pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex), ed.getSnapshot().getElementFirstRep()); ((PropertyWrapperDirect) pe).getWrapped().setStructure(ed); } results.add(pe); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java index 005da8090..fef5851a2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java @@ -8,6 +8,7 @@ import java.util.List; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; import org.hl7.fhir.r5.renderers.ResourceRenderer; @@ -28,6 +29,7 @@ public class BaseWrappers { public int getMinCardinality(); public int getMaxCardinality(); public StructureDefinition getStructure(); + public ElementDefinition getElementDefinition(); public BaseWrapper value(); public ResourceWrapper getAsResource(); public String fhirType(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java index da7d83da6..3fd8d6a7c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java @@ -206,6 +206,11 @@ public class DOMWrappers { return getTypeCode(); } + @Override + public ElementDefinition getElementDefinition() { + return definition; + } + } public static class ResourceWrapperElement extends WrapperBaseImpl implements ResourceWrapper { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java index 3cbe12568..7ec2aeb5b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java @@ -8,6 +8,7 @@ import java.util.List; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.Encounter; import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; import org.hl7.fhir.r5.model.Patient; @@ -29,6 +30,7 @@ public class DirectWrappers { public static class PropertyWrapperDirect extends RendererWrapperImpl implements PropertyWrapper { private Property wrapped; private List list; + private ElementDefinition ed; public PropertyWrapperDirect(RenderingContext context, Property wrapped) { super(context); @@ -37,6 +39,14 @@ public class DirectWrappers { this.wrapped = wrapped; } + public PropertyWrapperDirect(RenderingContext context, Property wrapped, ElementDefinition ed) { + super(context); + if (wrapped == null) + throw new Error("wrapped == null"); + this.wrapped = wrapped; + this.ed = ed; + } + @Override public String getName() { return wrapped.getName(); @@ -106,6 +116,11 @@ public class DirectWrappers { public String fhirType() { return wrapped.getTypeCode(); } + + @Override + public ElementDefinition getElementDefinition() { + return ed; + } } public static class BaseWrapperDirect extends WrapperBaseImpl implements BaseWrapper { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java index b21cd002b..2ca58caf5 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java @@ -58,7 +58,7 @@ public class ElementWrappers { throw new FHIRException(e.getMessage(), e); } if (context.getParser() == null) { - System.out.println("Noe version specific parser provided"); + System.out.println("No version specific parser provided"); } if (context.getParser() == null) { throw new Error("No type parser provided to renderer context"); @@ -324,6 +324,11 @@ public class ElementWrappers { return getTypeCode(); } + @Override + public ElementDefinition getElementDefinition() { + return definition; + } + } } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java index 419c88212..936bb7cdd 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java @@ -741,6 +741,12 @@ public class XhtmlNode implements IBaseXhtml { } + public XhtmlNode backgroundColor(String color) { + style("background-color: "+color); + return this; + } + + From 2adb9a7b685a958e8bf04a1e88ef2c15fe7813a4 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 4 Sep 2020 16:23:27 +1000 Subject: [PATCH 07/21] Add support for choice groups, and markdownify some elements --- .../fhir/r5/conformance/ProfileUtilities.java | 246 ++++++++++++++++-- .../org/hl7/fhir/r5/utils/FHIRPathEngine.java | 14 + 2 files changed, 239 insertions(+), 21 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 6192b70a3..9242d42fd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -78,6 +78,9 @@ import org.hl7.fhir.r5.model.Enumeration; import org.hl7.fhir.r5.model.Enumerations.BindingStrength; import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r5.model.ExpressionNode; +import org.hl7.fhir.r5.model.ExpressionNode.Kind; +import org.hl7.fhir.r5.model.ExpressionNode.Operation; import org.hl7.fhir.r5.model.Extension; import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.IntegerType; @@ -99,6 +102,8 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.r5.renderers.TerminologyRenderer; import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.r5.utils.FHIRLexer; +import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.TranslatingUtilities; import org.hl7.fhir.r5.utils.XVerExtensionManager; @@ -200,7 +205,31 @@ public class ProfileUtilities extends TranslatingUtilities { } } - + public static class ElementChoiceGroup { + private Row row; + private String name; + private boolean mandatory; + private List elements = new ArrayList<>(); + + public ElementChoiceGroup(String name, boolean mandatory) { + super(); + this.name = name; + this.mandatory = mandatory; + } + public Row getRow() { + return row; + } + public List getElements() { + return elements; + } + public void setRow(Row row) { + this.row = row; + } + public String getName() { + return name; + } + } + private static final int MAX_RECURSION_LIMIT = 10; public class ExtensionContext { @@ -268,6 +297,7 @@ public class ProfileUtilities extends TranslatingUtilities { // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here private final IWorkerContext context; + private FHIRPathEngine fpe; private List messages; private List snapshotStack = new ArrayList(); private ProfileKnowledgeProvider pkp; @@ -284,6 +314,9 @@ public class ProfileUtilities extends TranslatingUtilities { this.context = context; this.messages = messages; this.pkp = pkp; + if (context != null) { + this.fpe = new FHIRPathEngine(context, this); + } } public static class UnusedTracker { @@ -538,6 +571,7 @@ public class ProfileUtilities extends TranslatingUtilities { } processPaths("", derived.getSnapshot(), baseSnapshot, diff, baseCursor, diffCursor, baseSnapshot.getElement().size()-1, derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, null, new ArrayList(), base); + checkGroupConstraints(derived); if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { for (ElementDefinition e : diff.getElement()) { if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { @@ -666,6 +700,81 @@ public class ProfileUtilities extends TranslatingUtilities { derived.clearUserData("profileutils.snapshot.generating"); } + private void checkGroupConstraints(StructureDefinition derived) { + List toRemove = new ArrayList<>(); +// List processed = new ArrayList<>(); + for (ElementDefinition element : derived.getSnapshot().getElement()) { + if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) { + checkForChildrenInGroup(derived, toRemove, element); + } + } + derived.getSnapshot().getElement().removeAll(toRemove); + } + + public void checkForChildrenInGroup(StructureDefinition derived, List toRemove, ElementDefinition element) throws Error { + List children = getChildren(derived, element); + List groups = readChoices(element, children); + for (ElementChoiceGroup group : groups) { + System.out.println(children); + String mandated = null; + Set names = new HashSet<>(); + for (ElementDefinition ed : children) { + String name = tail(ed.getPath()); + if (names.contains(name)) { + throw new Error("huh?"); + } else { + names.add(name); + } + if (group.getElements().contains(name)) { + if (ed.getMin() == 1) { + if (mandated == null) { + mandated = name; + } else { + throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name); + } + } + } + } + if (mandated != null) { + for (ElementDefinition ed : children) { + String name = tail(ed.getPath()); + if (group.getElements().contains(name) && !mandated.equals(name)) { + ed.setMax("0"); + addAllChildren(derived, ed, toRemove); + } + } + } + } + } + + private List getChildren(StructureDefinition derived, ElementDefinition element) { + List elements = derived.getSnapshot().getElement(); + int index = elements.indexOf(element) + 1; + String path = element.getPath()+"."; + List list = new ArrayList<>(); + while (index < elements.size()) { + ElementDefinition e = elements.get(index); + String p = e.getPath(); + if (p.startsWith(path) && !e.hasSliceName()) { + if (!p.substring(path.length()).contains(".")) { + list.add(e); + } + index++; + } else { + break; + } + } + return list; + } + + private void addAllChildren(StructureDefinition derived, ElementDefinition element, List toRemove) { + List children = getChildList(derived, element); + for (ElementDefinition child : children) { + toRemove.add(child); + addAllChildren(derived, child, toRemove); + } + } + private void checkDifferential(List elements, String type, String url) { boolean first = true; for (ElementDefinition ed : elements) { @@ -3620,12 +3729,17 @@ public class ProfileUtilities extends TranslatingUtilities { } Row currRow = row; + List groups = readChoices(element, children); boolean isExtension = Utilities.existsInList(tail(element.getPath()), "extension", "modifierExtension"); for (ElementDefinition child : children) { - if (!child.hasSliceName()) + if (!child.hasSliceName()) { currRow = row; - if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) - currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport); + } + Row childRow = chooseChildRowByGroup(gen, currRow, groups, child, element, isConstraintMode); + + if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) { + currRow = genElement(defPath, gen, childRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport); + } } // if (!snapshot && (extensions == null || !extensions)) // for (ElementDefinition child : children) @@ -3639,6 +3753,34 @@ public class ProfileUtilities extends TranslatingUtilities { return slicingRow; } + private Row chooseChildRowByGroup(HierarchicalTableGenerator gen, Row row, List groups, ElementDefinition element, ElementDefinition parent, boolean isConstraintMode) { + String name = tail(element.getPath()); + for (ElementChoiceGroup grp : groups) { + if (grp.getElements().contains(name)) { + if (grp.getRow() == null) { + grp.setRow(makeChoiceElementRow(gen, row, grp, parent, isConstraintMode)); + } + return grp.getRow(); + } + } + return row; + } + + private Row makeChoiceElementRow(HierarchicalTableGenerator gen, Row prow, ElementChoiceGroup grp, ElementDefinition parent, boolean isConstraintMode) { + Row row = gen.new Row(); + row.setAnchor(parent.getPath()+"-"+grp.getName()); + row.setColor(getRowColor(parent, isConstraintMode)); + row.setLineColor(1); + row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); + row.getCells().add(gen.new Cell(null, null, "(Choice of one)", "", null)); + row.getCells().add(gen.new Cell()); + row.getCells().add(gen.new Cell(null, null, (grp.mandatory ? "1" : "0")+"..1", "", null)); + row.getCells().add(gen.new Cell()); + row.getCells().add(gen.new Cell()); + prow.getSubRows().add(row); + return row; + } + public Cell genElementNameCell(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef, boolean ext, UnusedTracker used, String ref, String sName) throws IOException { @@ -3985,7 +4127,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (root) { if (profile.getAbstract()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.addPiece(gen.new Piece(null, "This is an abstract profile", null)); } } @@ -3993,10 +4135,10 @@ public class ProfileUtilities extends TranslatingUtilities { c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); } else { if (definition != null && definition.hasShort()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null))); } else if (fallback != null && fallback.hasShort()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.addPiece(gen.new Piece(null, gt(fallback.getShortElement()), null).addStyle("opacity: 0.5")); } if (url != null) { @@ -4041,7 +4183,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (definition.hasSlicing()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold")); c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); } @@ -4095,7 +4237,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (definition.hasFixed()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold"))); if (!useTableForFixedValues || definition.getFixed().isPrimitive()) { String s = buildJson(definition.getFixed()); @@ -4113,7 +4255,7 @@ public class ProfileUtilities extends TranslatingUtilities { c.getPieces().add(p); } } else if (definition.hasPattern()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold"))); if (!useTableForFixedValues || definition.getPattern().isPrimitive()) c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); @@ -4123,13 +4265,13 @@ public class ProfileUtilities extends TranslatingUtilities { } } else if (definition.hasExample()) { for (ElementDefinitionExampleComponent ex : definition.getExample()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel())+": ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); } } if (definition.hasMaxLength() && definition.getMaxLength()!=0) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); } @@ -4367,7 +4509,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (definition.hasSlicing()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); } @@ -4389,12 +4531,16 @@ public class ProfileUtilities extends TranslatingUtilities { } } for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); - c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); + if (inv.getHumanElement().hasExtension("http://hl7.org/fhir/StructureDefinition/rendering-markdown")) { + c.addMarkdown(inv.getHumanElement().getExtensionString("http://hl7.org/fhir/StructureDefinition/rendering-markdown")); + } else { + c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); + } } if (definition.hasFixed()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); String s = buildJson(definition.getFixed()); String link = null; @@ -4402,18 +4548,18 @@ public class ProfileUtilities extends TranslatingUtilities { link = pkp.getLinkForUrl(corePath, s); c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); } else if (definition.hasPattern()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); } else if (definition.hasExample()) { for (ElementDefinitionExampleComponent ex : definition.getExample()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); } } if (definition.hasMaxLength() && definition.getMaxLength()!=0) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); } @@ -4434,14 +4580,14 @@ public class ProfileUtilities extends TranslatingUtilities { } } if (definition.hasDefinition()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold")); c.addPiece(gen.new Piece("br")); c.addMarkdown(definition.getDefinition()); // c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); } if (definition.getComment()!=null) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold")); c.addPiece(gen.new Piece("br")); c.addMarkdown(definition.getComment()); @@ -5935,6 +6081,64 @@ public class ProfileUtilities extends TranslatingUtilities { } + public List readChoices(ElementDefinition ed, List children) { + List result = new ArrayList<>(); + for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { + ElementChoiceGroup grp = processConstraint(children, c); + if (grp != null) { + result.add(grp); + } + } + return result; + } + + private ElementChoiceGroup processConstraint(List children, ElementDefinitionConstraintComponent c) { + if (!c.hasExpression()) { + return null; + } + ExpressionNode expr = fpe.parse(c.getExpression()); + if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) { + return null; + } + ExpressionNode n1 = expr.getGroup(); + ExpressionNode n2 = expr.getOpNext(); + if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) { + return null; + } + ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals); + while (n1 != null) { + if (n1.getKind() != Kind.Name || n1.getInner() != null) { + return null; + } + grp.elements.add(n1.getName()); + if (n1.getOperation() == null || n1.getOperation() == Operation.Union) { + n1 = n1.getOpNext(); + } else { + return null; + } + } + int total = 0; + for (String n : grp.elements) { + boolean found = false; + for (ElementDefinition child : children) { + String name = tail(child.getPath()); + if (n.equals(name)) { + found = true; + if (!"0".equals(child.getMax())) { + total++; + } + } + } + if (!found) { + return null; + } + } + if (total <= 1) { + return null; + } + return grp; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 7ce5d015e..e16776f40 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -319,6 +319,20 @@ public class FHIRPathEngine { } } + public FHIRPathEngine(IWorkerContext worker, ProfileUtilities utilities) { + super(); + this.worker = worker; + profileUtilities = utilities; + for (StructureDefinition sd : worker.getStructures()) { + if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) { + allTypes.put(sd.getName(), sd); + } + if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { + primitiveTypes.add(sd.getName()); + } + } + } + // --- 3 methods to override in children ------------------------------------------------------- // if you don't override, it falls through to the using the base reference implementation From 7ca698f36a7c47c38b168c1e03ea6190d9104ae3 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 4 Sep 2020 23:49:36 +1000 Subject: [PATCH 08/21] fixes for 5.1.8 (#336) * Fix up conversion for date problems * Mark packages as unsuitable for publication * rework rendering tests & fix bug in Parameters renderer * fix test case depedency * update VSACImporter to get value sets from VSAC directly * Fix up rendering of Extensions * Add support for choice groups, and markdownify some elements --- .../convertors/VersionConvertor_10_40.java | 7 + .../conv10_40/MedicationRequest10_40.java | 6 +- .../fhir/convertors/misc/VSACImporter.java | 149 ++++++----- .../fhir/r5/conformance/ProfileUtilities.java | 246 ++++++++++++++++-- .../main/java/org/hl7/fhir/r5/model/Base.java | 3 +- .../hl7/fhir/r5/renderers/DataRenderer.java | 102 ++++++++ .../fhir/r5/renderers/ParametersRenderer.java | 10 +- .../r5/renderers/ProfileDrivenRenderer.java | 154 +++++++---- .../fhir/r5/renderers/RendererFactory.java | 3 + .../fhir/r5/renderers/utils/BaseWrappers.java | 8 +- .../fhir/r5/renderers/utils/DOMWrappers.java | 5 + .../r5/renderers/utils/DirectWrappers.java | 15 ++ .../r5/renderers/utils/ElementWrappers.java | 7 +- .../org/hl7/fhir/r5/utils/FHIRPathEngine.java | 14 + .../fhir/r5/utils/NPMPackageGenerator.java | 49 ++-- .../r5/test/NarrativeGenerationTests.java | 60 ++++- .../fhir/r5/test/ResourceRoundTripTests.java | 3 + .../hl7/fhir/utilities/cache/NpmPackage.java | 4 + .../hl7/fhir/utilities/xhtml/XhtmlNode.java | 6 + pom.xml | 2 +- 20 files changed, 675 insertions(+), 178 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_10_40.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_10_40.java index 431207402..c9550402d 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_10_40.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_10_40.java @@ -65,6 +65,7 @@ import org.hl7.fhir.convertors.conv10_40.ValueSet10_40; import org.hl7.fhir.dstu2.model.CodeableConcept; import org.hl7.fhir.dstu2.model.Parameters; import org.hl7.fhir.dstu2.model.Parameters.ParametersParameterComponent; +import org.hl7.fhir.dstu2.model.PositiveIntType; import org.hl7.fhir.dstu2.model.Reference; import org.hl7.fhir.dstu2.utils.ToolingExtensions; import org.hl7.fhir.exceptions.FHIRException; @@ -3423,4 +3424,10 @@ public class VersionConvertor_10_40 { public static org.hl7.fhir.dstu2.model.Resource convertResource(org.hl7.fhir.r4.model.Resource src) throws FHIRException { return convertResource(src, null); } + + public static UnsignedIntType convertUnsignedIntToPositive(PositiveIntType src) { + org.hl7.fhir.r4.model.UnsignedIntType tgt = src.hasValue() ? new org.hl7.fhir.r4.model.UnsignedIntType(src.getValue()) : new org.hl7.fhir.r4.model.UnsignedIntType(); + copyElement(src, tgt); + return tgt; + } } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/MedicationRequest10_40.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/MedicationRequest10_40.java index 25e745d33..e0e3bbed0 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/MedicationRequest10_40.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/MedicationRequest10_40.java @@ -15,7 +15,7 @@ public class MedicationRequest10_40 { tgt.setIntent(org.hl7.fhir.r4.model.MedicationRequest.MedicationRequestIntent.ORDER); for (org.hl7.fhir.dstu2.model.Identifier identifier : src.getIdentifier()) tgt.addIdentifier(VersionConvertor_10_40.convertIdentifier(identifier)); if (src.hasDateWritten()) - tgt.setAuthoredOn(src.getDateWritten()); + tgt.setAuthoredOnElement(VersionConvertor_10_40.convertDateTime(src.getDateWrittenElement())); if (src.hasStatus()) tgt.setStatus(org.hl7.fhir.r4.model.MedicationRequest.MedicationRequestStatus.fromCode(src.getStatus().toCode())); if (src.hasPatient()) @@ -48,7 +48,7 @@ public class MedicationRequest10_40 { return null; org.hl7.fhir.r4.model.Dosage tgt = new org.hl7.fhir.r4.model.Dosage(); if (src.hasText()) - tgt.setText(src.getText()); + tgt.setTextElement(VersionConvertor_10_40.convertString(src.getTextElement())); if (src.hasAdditionalInstructions()) tgt.addAdditionalInstruction(VersionConvertor_10_40.convertCodeableConcept(src.getAdditionalInstructions())); if (src.hasTiming()) @@ -79,7 +79,7 @@ public class MedicationRequest10_40 { if (src.hasValidityPeriod()) tgt.setValidityPeriod(VersionConvertor_10_40.convertPeriod(src.getValidityPeriod())); if (src.hasNumberOfRepeatsAllowed()) - tgt.setNumberOfRepeatsAllowed(src.getNumberOfRepeatsAllowed()); + tgt.setNumberOfRepeatsAllowedElement(VersionConvertor_10_40.convertUnsignedIntToPositive(src.getNumberOfRepeatsAllowedElement())); if (src.hasQuantity()) tgt.setQuantity(VersionConvertor_10_40.convertSimpleQuantity(src.getQuantity())); if (src.hasExpectedSupplyDuration()) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java index 2e03d4430..dd791b3db 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java @@ -1,26 +1,29 @@ package org.hl7.fhir.convertors.misc; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r5.formats.JsonParser; -import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r4.utils.client.FHIRToolingClient; +import org.hl7.fhir.r4.formats.JsonParser; +import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.utilities.CSVReader; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.xml.XMLUtil; import org.w3c.dom.Element; import java.io.*; +import java.net.URISyntaxException; import java.text.ParseException; import java.util.ArrayList; import java.util.List; public class VSACImporter extends OIDBasedValueSetImporter { - public static void main(String[] args) throws FileNotFoundException, FHIRException, IOException, ParseException { + public static void main(String[] args) throws FileNotFoundException, FHIRException, IOException, ParseException, URISyntaxException { // new PhinVadsImporter().importValueSet(TextFile.fileToBytes("C:\\work\\org.hl7.fhir\\packages\\us.cdc.phinvads-source\\source\\PHVS_BirthDefectsLateralityatDiagnosis_HL7_V1.txt")); VSACImporter self = new VSACImporter(); - self.process(args[0], args[1]); + self.process(args[0], args[1], args[2], args[3]); } public VSACImporter() throws FileNotFoundException, FHIRException, IOException { @@ -28,69 +31,87 @@ public class VSACImporter extends OIDBasedValueSetImporter { init(); } - private void process(String source, String dest) { - for (File f : new File(source).listFiles()) { + private void process(String source, String dest, String username, String password) throws FHIRException, FileNotFoundException, IOException, URISyntaxException { + CSVReader csv = new CSVReader(new FileInputStream(source)); + csv.readHeaders(); + FHIRToolingClient client = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir", username, password); + int i = 0; + while (csv.line()) { + String oid = csv.cell("OID"); try { - System.out.println("Process " + f.getName()); - List vsl = importValueSet(TextFile.fileToBytes(f)); - for (ValueSet vs : vsl) { - if (vs.getId() != null) { - new JsonParser().compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + vs.getId() + ".json")), vs); - } + ValueSet vs = client.read(ValueSet.class, oid); + new JsonParser().compose(new FileOutputStream(Utilities.path(dest, "ValueSet-"+oid+".json")), vs); + i++; + if (i % 100 == 0) { + System.out.println(i); } } catch (Exception e) { - e.printStackTrace(); + System.out.println("Unable to fetch OID "+oid+": "+e.getMessage()); } } + System.out.println("Done. "+i+" ValueSets"); +// for (File f : new File(source).listFiles()) { +// try { +// System.out.println("Process " + f.getName()); +// List vsl = importValueSet(TextFile.fileToBytes(f)); +// for (ValueSet vs : vsl) { +// if (vs.getId() != null) { +// new JsonParser().compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + vs.getId() + ".json")), vs); +// } +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } } - private List importValueSet(byte[] source) throws Exception { - List res = new ArrayList(); - Element x = loadXml(new ByteArrayInputStream(source)).getDocumentElement(); - List vl = XMLUtil.getNamedChildren(x, "DescribedValueSet"); - for (Element v : vl) { - ValueSet vs = new ValueSet(); - vs.setId(v.getAttribute("ID")); - vs.setUrl("http://vsac.nlm.nih.gov/fhir/ValueSet/" + vs.getId()); - vs.getMeta().setSource("https://vsac.nlm.nih.gov/valueset/" + vs.getId() + "/expansion"); - vs.setVersion(v.getAttribute("version")); - vs.setTitle(v.getAttribute("displayName")); - vs.setName(Utilities.titleize(vs.getTitle()).replace(" ", "")); - Element d = XMLUtil.getNamedChild(v, "Purpose"); - if (d != null) { - vs.setDescription(d.getTextContent()); - } - Element s = XMLUtil.getNamedChild(v, "Status"); - if (s != null && "Active".equals(s.getTextContent())) { - vs.setStatus(PublicationStatus.ACTIVE); - } else { - vs.setStatus(PublicationStatus.DRAFT); - } - Element dt = XMLUtil.getNamedChild(v, "RevisionDate"); - if (dt != null) { - vs.getDateElement().setValueAsString(dt.getTextContent()); - } - - Element cl = XMLUtil.getNamedChild(v, "ConceptList"); - Element cc = XMLUtil.getFirstChild(cl); - - while (cc != null) { - String code = cc.getAttribute("code"); - String display = cc.getAttribute("displayName"); - String csoid = cc.getAttribute("codeSystem"); - String csver = cc.getAttribute("codeSystemVersion"); - String url = context.oid2Uri(csoid); - if (url == null) { - url = "urn:oid:" + csoid; - } - csver = fixVersionforSystem(url, csver); - ConceptSetComponent inc = getInclude(vs, url, csver); - inc.addConcept().setCode(code).setDisplay(display); - cc = XMLUtil.getNextSibling(cc); - } - - res.add(vs); - } - return res; - } +// private List importValueSet(byte[] source) throws Exception { +// List res = new ArrayList(); +// Element x = loadXml(new ByteArrayInputStream(source)).getDocumentElement(); +// List vl = XMLUtil.getNamedChildren(x, "DescribedValueSet"); +// for (Element v : vl) { +// ValueSet vs = new ValueSet(); +// vs.setId(v.getAttribute("ID")); +// vs.setUrl("http://cts.nlm.nih.gov/fhir/ValueSet/" + vs.getId()); +// vs.getMeta().setSource("https://vsac.nlm.nih.gov/valueset/" + vs.getId() + "/expansion"); +// vs.setVersion(v.getAttribute("version")); +// vs.setTitle(v.getAttribute("displayName")); +// vs.setName(Utilities.titleize(vs.getTitle()).replace(" ", "")); +// Element d = XMLUtil.getNamedChild(v, "Purpose"); +// if (d != null) { +// vs.setDescription(d.getTextContent()); +// } +// Element s = XMLUtil.getNamedChild(v, "Status"); +// if (s != null && "Active".equals(s.getTextContent())) { +// vs.setStatus(PublicationStatus.ACTIVE); +// } else { +// vs.setStatus(PublicationStatus.DRAFT); +// } +// Element dt = XMLUtil.getNamedChild(v, "RevisionDate"); +// if (dt != null) { +// vs.getDateElement().setValueAsString(dt.getTextContent()); +// } +// +// Element cl = XMLUtil.getNamedChild(v, "ConceptList"); +// Element cc = XMLUtil.getFirstChild(cl); +// +// while (cc != null) { +// String code = cc.getAttribute("code"); +// String display = cc.getAttribute("displayName"); +// String csoid = cc.getAttribute("codeSystem"); +// String csver = cc.getAttribute("codeSystemVersion"); +// String url = context.oid2Uri(csoid); +// if (url == null) { +// url = "urn:oid:" + csoid; +// } +// csver = fixVersionforSystem(url, csver); +// ConceptSetComponent inc = getInclude(vs, url, csver); +// inc.addConcept().setCode(code).setDisplay(display); +// cc = XMLUtil.getNextSibling(cc); +// } +// +// res.add(vs); +// } +// return res; +// } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 6192b70a3..9242d42fd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -78,6 +78,9 @@ import org.hl7.fhir.r5.model.Enumeration; import org.hl7.fhir.r5.model.Enumerations.BindingStrength; import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r5.model.ExpressionNode; +import org.hl7.fhir.r5.model.ExpressionNode.Kind; +import org.hl7.fhir.r5.model.ExpressionNode.Operation; import org.hl7.fhir.r5.model.Extension; import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.IntegerType; @@ -99,6 +102,8 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.r5.renderers.TerminologyRenderer; import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.r5.utils.FHIRLexer; +import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.TranslatingUtilities; import org.hl7.fhir.r5.utils.XVerExtensionManager; @@ -200,7 +205,31 @@ public class ProfileUtilities extends TranslatingUtilities { } } - + public static class ElementChoiceGroup { + private Row row; + private String name; + private boolean mandatory; + private List elements = new ArrayList<>(); + + public ElementChoiceGroup(String name, boolean mandatory) { + super(); + this.name = name; + this.mandatory = mandatory; + } + public Row getRow() { + return row; + } + public List getElements() { + return elements; + } + public void setRow(Row row) { + this.row = row; + } + public String getName() { + return name; + } + } + private static final int MAX_RECURSION_LIMIT = 10; public class ExtensionContext { @@ -268,6 +297,7 @@ public class ProfileUtilities extends TranslatingUtilities { // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here private final IWorkerContext context; + private FHIRPathEngine fpe; private List messages; private List snapshotStack = new ArrayList(); private ProfileKnowledgeProvider pkp; @@ -284,6 +314,9 @@ public class ProfileUtilities extends TranslatingUtilities { this.context = context; this.messages = messages; this.pkp = pkp; + if (context != null) { + this.fpe = new FHIRPathEngine(context, this); + } } public static class UnusedTracker { @@ -538,6 +571,7 @@ public class ProfileUtilities extends TranslatingUtilities { } processPaths("", derived.getSnapshot(), baseSnapshot, diff, baseCursor, diffCursor, baseSnapshot.getElement().size()-1, derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, null, new ArrayList(), base); + checkGroupConstraints(derived); if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { for (ElementDefinition e : diff.getElement()) { if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { @@ -666,6 +700,81 @@ public class ProfileUtilities extends TranslatingUtilities { derived.clearUserData("profileutils.snapshot.generating"); } + private void checkGroupConstraints(StructureDefinition derived) { + List toRemove = new ArrayList<>(); +// List processed = new ArrayList<>(); + for (ElementDefinition element : derived.getSnapshot().getElement()) { + if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) { + checkForChildrenInGroup(derived, toRemove, element); + } + } + derived.getSnapshot().getElement().removeAll(toRemove); + } + + public void checkForChildrenInGroup(StructureDefinition derived, List toRemove, ElementDefinition element) throws Error { + List children = getChildren(derived, element); + List groups = readChoices(element, children); + for (ElementChoiceGroup group : groups) { + System.out.println(children); + String mandated = null; + Set names = new HashSet<>(); + for (ElementDefinition ed : children) { + String name = tail(ed.getPath()); + if (names.contains(name)) { + throw new Error("huh?"); + } else { + names.add(name); + } + if (group.getElements().contains(name)) { + if (ed.getMin() == 1) { + if (mandated == null) { + mandated = name; + } else { + throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name); + } + } + } + } + if (mandated != null) { + for (ElementDefinition ed : children) { + String name = tail(ed.getPath()); + if (group.getElements().contains(name) && !mandated.equals(name)) { + ed.setMax("0"); + addAllChildren(derived, ed, toRemove); + } + } + } + } + } + + private List getChildren(StructureDefinition derived, ElementDefinition element) { + List elements = derived.getSnapshot().getElement(); + int index = elements.indexOf(element) + 1; + String path = element.getPath()+"."; + List list = new ArrayList<>(); + while (index < elements.size()) { + ElementDefinition e = elements.get(index); + String p = e.getPath(); + if (p.startsWith(path) && !e.hasSliceName()) { + if (!p.substring(path.length()).contains(".")) { + list.add(e); + } + index++; + } else { + break; + } + } + return list; + } + + private void addAllChildren(StructureDefinition derived, ElementDefinition element, List toRemove) { + List children = getChildList(derived, element); + for (ElementDefinition child : children) { + toRemove.add(child); + addAllChildren(derived, child, toRemove); + } + } + private void checkDifferential(List elements, String type, String url) { boolean first = true; for (ElementDefinition ed : elements) { @@ -3620,12 +3729,17 @@ public class ProfileUtilities extends TranslatingUtilities { } Row currRow = row; + List groups = readChoices(element, children); boolean isExtension = Utilities.existsInList(tail(element.getPath()), "extension", "modifierExtension"); for (ElementDefinition child : children) { - if (!child.hasSliceName()) + if (!child.hasSliceName()) { currRow = row; - if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) - currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport); + } + Row childRow = chooseChildRowByGroup(gen, currRow, groups, child, element, isConstraintMode); + + if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) { + currRow = genElement(defPath, gen, childRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport); + } } // if (!snapshot && (extensions == null || !extensions)) // for (ElementDefinition child : children) @@ -3639,6 +3753,34 @@ public class ProfileUtilities extends TranslatingUtilities { return slicingRow; } + private Row chooseChildRowByGroup(HierarchicalTableGenerator gen, Row row, List groups, ElementDefinition element, ElementDefinition parent, boolean isConstraintMode) { + String name = tail(element.getPath()); + for (ElementChoiceGroup grp : groups) { + if (grp.getElements().contains(name)) { + if (grp.getRow() == null) { + grp.setRow(makeChoiceElementRow(gen, row, grp, parent, isConstraintMode)); + } + return grp.getRow(); + } + } + return row; + } + + private Row makeChoiceElementRow(HierarchicalTableGenerator gen, Row prow, ElementChoiceGroup grp, ElementDefinition parent, boolean isConstraintMode) { + Row row = gen.new Row(); + row.setAnchor(parent.getPath()+"-"+grp.getName()); + row.setColor(getRowColor(parent, isConstraintMode)); + row.setLineColor(1); + row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE); + row.getCells().add(gen.new Cell(null, null, "(Choice of one)", "", null)); + row.getCells().add(gen.new Cell()); + row.getCells().add(gen.new Cell(null, null, (grp.mandatory ? "1" : "0")+"..1", "", null)); + row.getCells().add(gen.new Cell()); + row.getCells().add(gen.new Cell()); + prow.getSubRows().add(row); + return row; + } + public Cell genElementNameCell(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef, boolean ext, UnusedTracker used, String ref, String sName) throws IOException { @@ -3985,7 +4127,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (root) { if (profile.getAbstract()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.addPiece(gen.new Piece(null, "This is an abstract profile", null)); } } @@ -3993,10 +4135,10 @@ public class ProfileUtilities extends TranslatingUtilities { c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen"))); } else { if (definition != null && definition.hasShort()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null))); } else if (fallback != null && fallback.hasShort()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.addPiece(gen.new Piece(null, gt(fallback.getShortElement()), null).addStyle("opacity: 0.5")); } if (url != null) { @@ -4041,7 +4183,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (definition.hasSlicing()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold")); c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); } @@ -4095,7 +4237,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (definition.hasFixed()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold"))); if (!useTableForFixedValues || definition.getFixed().isPrimitive()) { String s = buildJson(definition.getFixed()); @@ -4113,7 +4255,7 @@ public class ProfileUtilities extends TranslatingUtilities { c.getPieces().add(p); } } else if (definition.hasPattern()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold"))); if (!useTableForFixedValues || definition.getPattern().isPrimitive()) c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); @@ -4123,13 +4265,13 @@ public class ProfileUtilities extends TranslatingUtilities { } } else if (definition.hasExample()) { for (ElementDefinitionExampleComponent ex : definition.getExample()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel())+": ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); } } if (definition.hasMaxLength() && definition.getMaxLength()!=0) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); } @@ -4367,7 +4509,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (definition.hasSlicing()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold")); c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null)); } @@ -4389,12 +4531,16 @@ public class ProfileUtilities extends TranslatingUtilities { } } for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold"))); - c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); + if (inv.getHumanElement().hasExtension("http://hl7.org/fhir/StructureDefinition/rendering-markdown")) { + c.addMarkdown(inv.getHumanElement().getExtensionString("http://hl7.org/fhir/StructureDefinition/rendering-markdown")); + } else { + c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null))); + } } if (definition.hasFixed()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold"))); String s = buildJson(definition.getFixed()); String link = null; @@ -4402,18 +4548,18 @@ public class ProfileUtilities extends TranslatingUtilities { link = pkp.getLinkForUrl(corePath, s); c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen"))); } else if (definition.hasPattern()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen"))); } else if (definition.hasExample()) { for (ElementDefinitionExampleComponent ex : definition.getExample()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen"))); } } if (definition.hasMaxLength() && definition.getMaxLength()!=0) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold"))); c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen"))); } @@ -4434,14 +4580,14 @@ public class ProfileUtilities extends TranslatingUtilities { } } if (definition.hasDefinition()) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold")); c.addPiece(gen.new Piece("br")); c.addMarkdown(definition.getDefinition()); // c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null))); } if (definition.getComment()!=null) { - if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br")); + if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); } c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold")); c.addPiece(gen.new Piece("br")); c.addMarkdown(definition.getComment()); @@ -5935,6 +6081,64 @@ public class ProfileUtilities extends TranslatingUtilities { } + public List readChoices(ElementDefinition ed, List children) { + List result = new ArrayList<>(); + for (ElementDefinitionConstraintComponent c : ed.getConstraint()) { + ElementChoiceGroup grp = processConstraint(children, c); + if (grp != null) { + result.add(grp); + } + } + return result; + } + + private ElementChoiceGroup processConstraint(List children, ElementDefinitionConstraintComponent c) { + if (!c.hasExpression()) { + return null; + } + ExpressionNode expr = fpe.parse(c.getExpression()); + if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) { + return null; + } + ExpressionNode n1 = expr.getGroup(); + ExpressionNode n2 = expr.getOpNext(); + if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) { + return null; + } + ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals); + while (n1 != null) { + if (n1.getKind() != Kind.Name || n1.getInner() != null) { + return null; + } + grp.elements.add(n1.getName()); + if (n1.getOperation() == null || n1.getOperation() == Operation.Union) { + n1 = n1.getOpNext(); + } else { + return null; + } + } + int total = 0; + for (String n : grp.elements) { + boolean found = false; + for (ElementDefinition child : children) { + String name = tail(child.getPath()); + if (n.equals(name)) { + found = true; + if (!"0".equals(child.getMax())) { + total++; + } + } + } + if (!found) { + return null; + } + } + if (total <= 1) { + return null; + } + return grp; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java index 13ceb36c3..c7f140781 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java @@ -171,8 +171,9 @@ private Map userData; List children = new ArrayList(); listChildren(children); for (Property c : children) - if (c.getName().equals(name)) + if (c.getName().equals(name) || c.getName().equals(name+"[x]")) { return c; + } return null; } 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 a51bb8a9e..b5dce979b 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 @@ -14,10 +14,16 @@ import org.hl7.fhir.r5.model.Annotation; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.BaseDateTimeType; import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.ContactPoint; +import org.hl7.fhir.r5.model.DataRequirement; +import org.hl7.fhir.r5.model.DataRequirement.DataRequirementCodeFilterComponent; +import org.hl7.fhir.r5.model.DataRequirement.DataRequirementDateFilterComponent; +import org.hl7.fhir.r5.model.DataRequirement.DataRequirementSortComponent; +import org.hl7.fhir.r5.model.DataRequirement.SortDirection; import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.DateTimeType; @@ -778,6 +784,102 @@ public class DataRenderer extends Renderer { x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay()); } + public void renderDataRequirement(XhtmlNode x, DataRequirement dr) { + XhtmlNode tbl = x.table("grid"); + XhtmlNode tr = tbl.tr(); + XhtmlNode td = tr.td().colspan("2"); + td.b().tx("Type"); + td.tx(": "); + StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.getType().toCode()); + if (sd != null && sd.hasUserData("path")) { + td.ah(sd.getUserString("path")).tx(dr.getType().toCode()); + } else { + td.tx(dr.getType().toCode()); + } + if (dr.hasProfile()) { + td.tx(" ("); + boolean first = true; + for (CanonicalType p : dr.getProfile()) { + if (first) first = false; else td.tx(" | "); + sd = context.getWorker().fetchResource(StructureDefinition.class, p.getValue()); + if (sd != null && sd.hasUserData("path")) { + td.ah(sd.getUserString("path")).tx(sd.present()); + } else { + td.tx(p.asStringValue()); + } + } + td.tx(")"); + } + if (dr.hasSubject()) { + tr = tbl.tr(); + td = tr.td().colspan("2"); + td.b().tx("Subject"); + if (dr.hasSubjectReference()) { + renderReference(td, dr.getSubjectReference()); + } else { + renderCodeableConcept(td, dr.getSubjectCodeableConcept()); + } + } + if (dr.hasCodeFilter() || dr.hasDateFilter()) { + tr = tbl.tr().backgroundColor("#efefef"); + tr.td().tx("Filter"); + tr.td().tx("Value"); + } + for (DataRequirementCodeFilterComponent cf : dr.getCodeFilter()) { + tr = tbl.tr(); + if (cf.hasPath()) { + tr.td().tx(cf.getPath()); + } else { + tr.td().tx("Search on " +cf.getSearchParam()); + } + if (cf.hasValueSet()) { + td = tr.td(); + td.tx("In ValueSet "); + render(td, cf.getValueSetElement()); + } else { + boolean first = true; + td = tr.td(); + td.tx("One of these codes: "); + for (Coding c : cf.getCode()) { + if (first) first = false; else td.tx(", "); + render(td, c); + } + } + } + for (DataRequirementDateFilterComponent cf : dr.getDateFilter()) { + tr = tbl.tr(); + if (cf.hasPath()) { + tr.td().tx(cf.getPath()); + } else { + tr.td().tx("Search on " +cf.getSearchParam()); + } + render(tr.td(), cf.getValue()); + } + if (dr.hasSort() || dr.hasLimit()) { + tr = tbl.tr(); + td = tr.td().colspan("2"); + if (dr.hasLimit()) { + td.b().tx("Limit"); + td.tx(": "); + td.tx(dr.getLimit()); + if (dr.hasSort()) { + td.tx(", "); + } + } + if (dr.hasSort()) { + td.b().tx("Sort"); + td.tx(": "); + boolean first = true; + for (DataRequirementSortComponent p : dr.getSort()) { + if (first) first = false; else td.tx(" | "); + td.tx(p.getDirection() == SortDirection.ASCENDING ? "+" : "-"); + td.tx(p.getPath()); + } + } + } + } + + private String displayTiming(Timing s) throws FHIRException { CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); if (s.hasCode()) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java index fc8e6436b..d6fcf528e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java @@ -31,13 +31,19 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class ParametersRenderer extends ResourceRenderer { + public ParametersRenderer(RenderingContext context) { + super(context); + } + public ParametersRenderer(RenderingContext context, ResourceContext rcontext) { super(context, rcontext); } - @Override public boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { + x.h2().tx("Parameters"); + XhtmlNode tbl = x.table("grid"); + params(tbl, ((Parameters) r).getParameter(), 0); return false; } @@ -87,7 +93,7 @@ public class ParametersRenderer extends ResourceRenderer { } } else if (p.has("part")) { tr.td(); - PropertyWrapper pw = getProperty(p, "parameter"); + PropertyWrapper pw = getProperty(p, "part"); paramsW(tbl, pw.getValues(), 1); } } 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 9b5841bb0..88870997d 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 @@ -27,6 +27,7 @@ import org.hl7.fhir.r5.model.CodeableReference; import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.ContactDetail; import org.hl7.fhir.r5.model.ContactPoint; +import org.hl7.fhir.r5.model.DataRequirement; import org.hl7.fhir.r5.model.DateTimeType; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Dosage; @@ -82,6 +83,7 @@ import org.w3c.dom.Element; public class ProfileDrivenRenderer extends ResourceRenderer { private Set containedIds = new HashSet<>(); + private boolean hasExtensions; public ProfileDrivenRenderer(RenderingContext context, ResourceContext rcontext) { super(context, rcontext); @@ -108,12 +110,13 @@ public class ProfileDrivenRenderer extends ResourceRenderer { System.out.println("hah!"); } containedIds.clear(); + hasExtensions = false; generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), false, 0); } catch (Exception e) { e.printStackTrace(); x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); } - return false; + return hasExtensions; } @Override @@ -378,6 +381,9 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } } else if (e instanceof Resource) { return; + } else if (e instanceof DataRequirement) { + DataRequirement p = (DataRequirement) e; + renderDataRequirement(x, p); } else if (e instanceof ElementDefinition) { x.tx("todo-bundle"); } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) { @@ -386,7 +392,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet, and no structure found"); else generateByProfile(res, sd, ew, sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep(), - getChildrenForPath(sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep().getPath()), x, path, showCodeDetails, indent + 1); + getChildrenForPath(sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep().getPath()), x, e.fhirType(), showCodeDetails, indent + 1); } } @@ -612,65 +618,98 @@ public class ProfileDrivenRenderer extends ResourceRenderer { 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) { - Map displayHints = readDisplayHints(child); - if ("DomainResource.contained".equals(child.getBase().getPath())) { + generateElementByProfile(res, profile, allElements, x, path, showCodeDetails, indent, p, child); + } + } + } + } + } + + 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 { + Map displayHints = readDisplayHints(child); + if ("DomainResource.contained".equals(child.getBase().getPath())) { // if (p.getValues().size() > 0 && child != null) { // for (BaseWrapper v : p.getValues()) { // x.an(v.get("id").primitiveValue()); // } // } - } else if (!exemptFromRendering(child)) { - List grandChildren = getChildrenForPath(allElements, path+"."+p.getName()); - filterGrandChildren(grandChildren, path+"."+p.getName(), p); - if (p.getValues().size() > 0) { - if (isPrimitive(child)) { - XhtmlNode para = x.para(); - String name = p.getName(); - if (name.endsWith("[x]")) - name = name.substring(0, name.length() - 3); - if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { - para.b().addText(name); - para.tx(": "); - if (renderAsList(child) && p.getValues().size() > 1) { - XhtmlNode list = x.ul(); - for (BaseWrapper v : p.getValues()) - renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent); - } else { - boolean first = true; - for (BaseWrapper v : p.getValues()) { - if (first) - first = false; - else - para.tx(", "); - renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); - } - } - } - } else if (canDoTable(path, p, grandChildren)) { - x.addTag(getHeader()).addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); - XhtmlNode tbl = x.table( "grid"); - XhtmlNode tr = tbl.tr(); - tr.td().tx("-"); // work around problem with empty table rows - addColumnHeadings(tr, grandChildren); - for (BaseWrapper v : p.getValues()) { - if (v != null) { - tr = tbl.tr(); - tr.td().tx("*"); // work around problem with empty table rows - addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent); - } - } - } else { - for (BaseWrapper v : p.getValues()) { - if (v != null) { - XhtmlNode bq = x.addTag("blockquote"); - bq.para().b().addText(p.getName()); - generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails, indent+1); - } - } + } else if (!exemptFromRendering(child)) { + if (isExtension(p)) { + hasExtensions = true; + } + List grandChildren = getChildrenForPath(allElements, path+"."+p.getName()); + filterGrandChildren(grandChildren, path+"."+p.getName(), p); + if (p.getValues().size() > 0) { + if (isPrimitive(child)) { + XhtmlNode para = x.para(); + String name = p.getName(); + if (name.endsWith("[x]")) + name = name.substring(0, name.length() - 3); + if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { + para.b().addText(name); + para.tx(": "); + if (renderAsList(child) && p.getValues().size() > 1) { + XhtmlNode list = x.ul(); + for (BaseWrapper v : p.getValues()) + renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent); + } else { + boolean first = true; + for (BaseWrapper v : p.getValues()) { + if (first) + first = false; + else + para.tx(", "); + renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); + } + } + } + } else if (canDoTable(path, p, grandChildren)) { + x.addTag(getHeader()).addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); + XhtmlNode tbl = x.table( "grid"); + XhtmlNode tr = tbl.tr(); + tr.td().tx("-"); // work around problem with empty table rows + addColumnHeadings(tr, grandChildren); + for (BaseWrapper v : p.getValues()) { + if (v != null) { + tr = tbl.tr(); + tr.td().tx("*"); // work around problem with empty table rows + addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent); + } + } + } else if (isExtension(p)) { + for (BaseWrapper v : p.getValues()) { + if (v != null) { + PropertyWrapper vp = v.getChildByName("value"); + PropertyWrapper ev = v.getChildByName("extension"); + if (vp.hasValues()) { + BaseWrapper vv = vp.value(); + XhtmlNode para = x.para(); + para.b().addText(p.getStructure().present()); + para.tx(": "); + renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); + } else if (ev.hasValues()) { + XhtmlNode bq = x.addTag("blockquote"); + bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName()); + for (BaseWrapper vv : ev.getValues()) { + StructureDefinition ex = context.getWorker().fetchTypeDefinition("Extension"); + List children = getChildrenForPath(ex.getSnapshot().getElement(), "Extension"); + generateByProfile(res, ex, vv, allElements, child, children, bq, "Extension", showCodeDetails, indent+1); } } } + } + } else { + for (BaseWrapper v : p.getValues()) { + if (v != null) { + XhtmlNode bq = x.addTag("blockquote"); + bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName()); + generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails, indent+1); + } } } } @@ -699,6 +738,9 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } private boolean canDoTable(String path, PropertyWrapper p, List grandChildren) { + if (isExtension(p)) { + return false; + } for (ElementDefinition e : grandChildren) { List values = getValues(path, p, e); if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e)) @@ -707,6 +749,10 @@ public class ProfileDrivenRenderer extends ResourceRenderer { return true; } + public boolean isExtension(PropertyWrapper p) { + return p.getName().contains("extension["); + } + private boolean canCollapse(ElementDefinition e) { // we can collapse any data type @@ -792,10 +838,10 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) throw new DefinitionException("unknown extension "+url); // System.out.println("unknown extension "+url); - pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex)); + pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), ed.getSnapshot().getElementFirstRep()); } else { ElementDefinition def = ed.getSnapshot().getElement().get(0); - pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex)); + pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex), ed.getSnapshot().getElementFirstRep()); ((PropertyWrapperDirect) pe).getWrapped().setStructure(ed); } results.add(pe); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java index 9fb41cc12..5935729b4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java @@ -72,6 +72,9 @@ public class RendererFactory { if ("OperationOutcome".equals(resourceName)) { return new OperationOutcomeRenderer(context); } + if ("Parameters".equals(resourceName)) { + return new ParametersRenderer(context); + } return new ProfileDrivenRenderer(context); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java index b811ac7fe..fef5851a2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java @@ -8,6 +8,7 @@ import java.util.List; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; import org.hl7.fhir.r5.renderers.ResourceRenderer; @@ -28,6 +29,7 @@ public class BaseWrappers { public int getMinCardinality(); public int getMaxCardinality(); public StructureDefinition getStructure(); + public ElementDefinition getElementDefinition(); public BaseWrapper value(); public ResourceWrapper getAsResource(); public String fhirType(); @@ -87,7 +89,7 @@ public class BaseWrappers { @Override public boolean has(String name) { for (PropertyWrapper p : children()) { - if (p.getName().equals(name)) { + if (p.getName().equals(name) || p.getName().equals(name+"[x]") ) { return p.hasValues(); } } @@ -97,7 +99,7 @@ public class BaseWrappers { @Override public Base get(String name) throws UnsupportedEncodingException, FHIRException, IOException { for (PropertyWrapper p : children()) { - if (p.getName().equals(name)) { + if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { if (p.hasValues()) { return p.getValues().get(0).getBase(); } else { @@ -111,7 +113,7 @@ public class BaseWrappers { @Override public List children(String name) throws UnsupportedEncodingException, FHIRException, IOException { for (PropertyWrapper p : children()) { - if (p.getName().equals(name)) { + if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { List res = new ArrayList<>(); for (BaseWrapper b : p.getValues()) { res.add(b); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java index da7d83da6..3fd8d6a7c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java @@ -206,6 +206,11 @@ public class DOMWrappers { return getTypeCode(); } + @Override + public ElementDefinition getElementDefinition() { + return definition; + } + } public static class ResourceWrapperElement extends WrapperBaseImpl implements ResourceWrapper { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java index 3cbe12568..7ec2aeb5b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java @@ -8,6 +8,7 @@ import java.util.List; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.Encounter; import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; import org.hl7.fhir.r5.model.Patient; @@ -29,6 +30,7 @@ public class DirectWrappers { public static class PropertyWrapperDirect extends RendererWrapperImpl implements PropertyWrapper { private Property wrapped; private List list; + private ElementDefinition ed; public PropertyWrapperDirect(RenderingContext context, Property wrapped) { super(context); @@ -37,6 +39,14 @@ public class DirectWrappers { this.wrapped = wrapped; } + public PropertyWrapperDirect(RenderingContext context, Property wrapped, ElementDefinition ed) { + super(context); + if (wrapped == null) + throw new Error("wrapped == null"); + this.wrapped = wrapped; + this.ed = ed; + } + @Override public String getName() { return wrapped.getName(); @@ -106,6 +116,11 @@ public class DirectWrappers { public String fhirType() { return wrapped.getTypeCode(); } + + @Override + public ElementDefinition getElementDefinition() { + return ed; + } } public static class BaseWrapperDirect extends WrapperBaseImpl implements BaseWrapper { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java index b21cd002b..2ca58caf5 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java @@ -58,7 +58,7 @@ public class ElementWrappers { throw new FHIRException(e.getMessage(), e); } if (context.getParser() == null) { - System.out.println("Noe version specific parser provided"); + System.out.println("No version specific parser provided"); } if (context.getParser() == null) { throw new Error("No type parser provided to renderer context"); @@ -324,6 +324,11 @@ public class ElementWrappers { return getTypeCode(); } + @Override + public ElementDefinition getElementDefinition() { + return definition; + } + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 7ce5d015e..e16776f40 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -319,6 +319,20 @@ public class FHIRPathEngine { } } + public FHIRPathEngine(IWorkerContext worker, ProfileUtilities utilities) { + super(); + this.worker = worker; + profileUtilities = utilities; + for (StructureDefinition sd : worker.getStructures()) { + if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) { + allTypes.put(sd.getName(), sd); + } + if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { + primitiveTypes.add(sd.getName()); + } + } + } + // --- 3 methods to override in children ------------------------------------------------------- // if you don't override, it falls through to the using the base reference implementation diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java index 22a5d0c54..0a46a7fcf 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/NPMPackageGenerator.java @@ -104,17 +104,17 @@ public class NPMPackageGenerator { private NpmPackageIndexBuilder indexer; - public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, Date date) throws FHIRException, IOException { + public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, Date date, boolean notForPublication) throws FHIRException, IOException { super(); this.destFile = destFile; start(); List fhirVersion = new ArrayList<>(); for (Enumeration v : ig.getFhirVersion()) fhirVersion.add(v.asStringValue()); - buildPackageJson(canonical, kind, url, date, ig, fhirVersion); + buildPackageJson(canonical, kind, url, date, ig, fhirVersion, notForPublication); } - public static NPMPackageGenerator subset(NPMPackageGenerator master, String destFile, String id, String name, Date date) throws FHIRException, IOException { + public static NPMPackageGenerator subset(NPMPackageGenerator master, String destFile, String id, String name, Date date, boolean notForPublication) throws FHIRException, IOException { JsonObject p = master.packageJ.deepCopy(); p.remove("name"); p.addProperty("name", id); @@ -122,24 +122,30 @@ public class NPMPackageGenerator { p.addProperty("type", PackageType.SUBSET.getCode()); p.remove("title"); p.addProperty("title", name); + if (notForPublication) { + p.addProperty("notForPublication", true); + } - return new NPMPackageGenerator(destFile, p, date); + return new NPMPackageGenerator(destFile, p, date, notForPublication); } - public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, Date date, List fhirVersion) throws FHIRException, IOException { + public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, Date date, List fhirVersion, boolean notForPublication) throws FHIRException, IOException { super(); this.destFile = destFile; start(); - buildPackageJson(canonical, kind, url, date, ig, fhirVersion); + buildPackageJson(canonical, kind, url, date, ig, fhirVersion, notForPublication); } - public NPMPackageGenerator(String destFile, JsonObject npm, Date date) throws FHIRException, IOException { + public NPMPackageGenerator(String destFile, JsonObject npm, Date date, boolean notForPublication) throws FHIRException, IOException { super(); String dt = new SimpleDateFormat("yyyyMMddHHmmss").format(date); packageJ = npm; packageManifest = new JsonObject(); packageManifest.addProperty("version", npm.get("version").getAsString()); packageManifest.addProperty("date", dt); + if (notForPublication) { + packageManifest.addProperty("notForPublication", true); + } npm.addProperty("date", dt); packageManifest.addProperty("name", npm.get("name").getAsString()); this.destFile = destFile; @@ -152,19 +158,23 @@ public class NPMPackageGenerator { } } - private void buildPackageJson(String canonical, PackageType kind, String web, Date date, ImplementationGuide ig, List fhirVersion) throws FHIRException, IOException { + private void buildPackageJson(String canonical, PackageType kind, String web, Date date, ImplementationGuide ig, List fhirVersion, boolean notForPublication) throws FHIRException, IOException { String dtHuman = new SimpleDateFormat("EEE, MMM d, yyyy HH:mmZ", new Locale("en", "US")).format(date); String dt = new SimpleDateFormat("yyyyMMddHHmmss").format(date); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); - if (!ig.hasPackageId()) + if (!ig.hasPackageId()) { b.append("packageId"); - if (!ig.hasVersion()) + } + if (!ig.hasVersion()) { b.append("version"); - if (!ig.hasFhirVersion()) + } + if (!ig.hasFhirVersion()) { b.append("fhirVersion"); - if (!ig.hasLicense()) + } + if (!ig.hasLicense()) { b.append("license"); + } for (ImplementationGuideDependsOnComponent d : ig.getDependsOn()) { if (!d.hasVersion()) { b.append("dependsOn.version("+d.getUri()+")"); @@ -177,14 +187,20 @@ public class NPMPackageGenerator { npm.addProperty("tools-version", ToolsVersion.TOOLS_VERSION); npm.addProperty("type", kind.getCode()); npm.addProperty("date", dt); - if (ig.hasLicense()) + if (ig.hasLicense()) { npm.addProperty("license", ig.getLicense().toCode()); + } npm.addProperty("canonical", canonical); + if (notForPublication) { + npm.addProperty("notForPublication", true); + } npm.addProperty("url", web); - if (ig.hasTitle()) + if (ig.hasTitle()) { npm.addProperty("title", ig.getTitle()); - if (ig.hasDescription()) + } + if (ig.hasDescription()) { npm.addProperty("description", ig.getDescription()+ " (built "+dtHuman+timezone()+")"); + } JsonArray vl = new JsonArray(); npm.add("fhirVersions", vl); @@ -205,8 +221,9 @@ public class NPMPackageGenerator { dep.addProperty(d.getPackageId(), d.getVersion()); } } - if (ig.hasPublisher()) + if (ig.hasPublisher()) { npm.addProperty("author", ig.getPublisher()); + } JsonArray m = new JsonArray(); for (ContactDetail t : ig.getContact()) { String email = email(t.getTelecom()); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java index c320f912f..9011cd929 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java @@ -11,22 +11,32 @@ import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.SystemUtils; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.elementmodel.Manager; +import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.XmlParser; +import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Questionnaire; +import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.RendererFactory; import org.hl7.fhir.r5.renderers.ResourceRenderer; +import org.hl7.fhir.r5.renderers.utils.ElementWrappers; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.RenderingContext.ITypeParser; import org.hl7.fhir.r5.renderers.utils.RenderingContext.QuestionnaireRendererMode; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; +import org.hl7.fhir.r5.test.NarrativeGenerationTests.TestTypeParser; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.utilities.TerminologyServiceOptions; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.xhtml.XhtmlComposer; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; +import org.hl7.fhir.utilities.xhtml.XhtmlParser; import org.hl7.fhir.utilities.xml.XMLUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -40,6 +50,15 @@ import org.xml.sax.SAXException; public class NarrativeGenerationTests { + public class TestTypeParser implements ITypeParser { + + @Override + public Base parseType(String xml, String type) throws FHIRFormatError, IOException, FHIRException { + return new org.hl7.fhir.r5.formats.XmlParser().parseType(xml, type); + } + + } + public static final String WINDOWS = "WINDOWS"; private static final String HEADER = ""+ @@ -57,11 +76,13 @@ public class NarrativeGenerationTests { public static class TestDetails { private String id; private boolean header; + private boolean meta; public TestDetails(Element test) { super(); id = test.getAttribute("id"); header = "true".equals(test.getAttribute("header")); + meta = "true".equals(test.getAttribute("meta")); } public String getId() { @@ -70,6 +91,10 @@ public class NarrativeGenerationTests { public boolean isHeader() { return header; + } + + public boolean isMeta() { + return meta; } } @@ -107,19 +132,30 @@ public class NarrativeGenerationTests { rc.setHeader(test.isHeader()); rc.setDefinitionsTarget("test.html"); rc.setTerminologyServiceOptions(TerminologyServiceOptions.defaults()); - IOUtils.copy(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-expected.xml"), new FileOutputStream(TestingUtilities.tempFile("narrative", test.getId() + "-expected.xml"))); - DomainResource source; - if (TestingUtilities.findTestResource("r5", "narrative", test.getId() + "-input.json")) { - source = (DomainResource) new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-input.json")); + rc.setParser(new TestTypeParser()); + Resource source; + if (TestingUtilities.findTestResource("r5", "narrative", test.getId() + ".json")) { + source = (Resource) new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".json")); } else { - source = (DomainResource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-input.xml")); + source = (Resource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml")); + } + + XhtmlNode x = RendererFactory.factory(source, rc).build(source); + String target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".html")); + String output = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER; + TextFile.stringToFile(target, TestingUtilities.tempFile("narrative", test.getId() + ".target.html")); + TextFile.stringToFile(output, TestingUtilities.tempFile("narrative", test.getId() + ".output.html")); + Assertions.assertTrue(output.equals(target), "Output does not match expected"); + + if (test.isMeta()) { + org.hl7.fhir.r5.elementmodel.Element e = Manager.parse(context, TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml"), FhirFormat.XML); + x = RendererFactory.factory(source, rc).render(new ElementWrappers.ResourceWrapperMetaElement(rc, e)); + + target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html")); + output = HEADER+new XhtmlComposer(true).compose(x)+FOOTER; + TextFile.stringToFile(output, TestingUtilities.tempFile("narrative", test.getId() + "-meta.output.html")); + Assertions.assertTrue(output.equals(target), "Output does not match expected (meta)"); } - DomainResource target = (DomainResource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-expected.xml")); - RendererFactory.factory(source, rc).render(source); - new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilities.tempFile("narrative", test.getId() + "-actual.xml")), source); - source = (DomainResource) new XmlParser().parse(new FileInputStream(TestingUtilities.tempFile("narrative", test.getId() + "-actual.xml"))); - String html = HEADER+new XhtmlComposer(true).compose(source.getText().getDiv())+FOOTER; - TextFile.stringToFile(html, TestingUtilities.tempFile("narrative", test.getId() + ".html")); - Assertions.assertTrue(source.equalsDeep(target), "Output does not match expected"); } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java index 6406fb930..ba462e423 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java @@ -13,6 +13,7 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.DateTimeType; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.RendererFactory; @@ -20,6 +21,7 @@ import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.EOperationOutcome; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class ResourceRoundTripTests { @@ -52,4 +54,5 @@ public class ResourceRoundTripTests { if (result == null) throw new FHIRException("Bundle was null"); } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java index 75d298867..b87b60818 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java @@ -1091,6 +1091,10 @@ public class NpmPackage { } return true; } + + public boolean isNotForPublication() { + return JSONUtil.bool(npm, "notForPublication"); + } } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java index 419c88212..936bb7cdd 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java @@ -741,6 +741,12 @@ public class XhtmlNode implements IBaseXhtml { } + public XhtmlNode backgroundColor(String color) { + style("background-color: "+color); + return this; + } + + diff --git a/pom.xml b/pom.xml index b72385c45..611b9deb9 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 5.1.0 - 1.1.35 + 1.1.36-SNAPSHOT 5.6.2 3.0.0-M4 0.8.5 From f56235d5966bf37f76075b5c939c1ef191ace0bc Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 4 Sep 2020 23:52:52 +1000 Subject: [PATCH 09/21] rendering fixes (#334) * Fix up conversion for date problems * Mark packages as unsuitable for publication * rework rendering tests & fix bug in Parameters renderer * fix test case depedency From 222a4babe37c01b030578f9bf75f0a0d13707f66 Mon Sep 17 00:00:00 2001 From: Itay Goren Date: Sun, 6 Sep 2020 10:23:43 +0300 Subject: [PATCH 10/21] add copyDomainResource to allergy intolerance of version convertor 10 40 --- .../hl7/fhir/convertors/conv10_40/AllergyIntolerance10_40.java | 1 + .../src/test/resources/0_allergy_intolerance_40.json | 1 + .../src/test/resources/1_allergy_intolerance_40.json | 1 + 3 files changed, 3 insertions(+) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/AllergyIntolerance10_40.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/AllergyIntolerance10_40.java index e5c51c648..70ac34f1f 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/AllergyIntolerance10_40.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_40/AllergyIntolerance10_40.java @@ -9,6 +9,7 @@ public class AllergyIntolerance10_40 { if (src == null) return null; org.hl7.fhir.r4.model.AllergyIntolerance tgt = new org.hl7.fhir.r4.model.AllergyIntolerance(); + VersionConvertor_10_40.copyDomainResource(src, tgt); for (org.hl7.fhir.dstu2.model.Identifier t : src.getIdentifier()) tgt.addIdentifier(VersionConvertor_10_40.convertIdentifier(t)); if (src.hasOnset()) tgt.setOnset(VersionConvertor_10_40.convertType(src.getOnsetElement())); diff --git a/org.hl7.fhir.convertors/src/test/resources/0_allergy_intolerance_40.json b/org.hl7.fhir.convertors/src/test/resources/0_allergy_intolerance_40.json index e02782189..1fec970d1 100644 --- a/org.hl7.fhir.convertors/src/test/resources/0_allergy_intolerance_40.json +++ b/org.hl7.fhir.convertors/src/test/resources/0_allergy_intolerance_40.json @@ -1,5 +1,6 @@ { "resourceType" : "AllergyIntolerance", + "id" : "TBwnNbrAqC0Qw5Ha7AFT-2AB", "clinicalStatus" : { "coding" : [ { diff --git a/org.hl7.fhir.convertors/src/test/resources/1_allergy_intolerance_40.json b/org.hl7.fhir.convertors/src/test/resources/1_allergy_intolerance_40.json index a38da25a8..fb2cb4e64 100644 --- a/org.hl7.fhir.convertors/src/test/resources/1_allergy_intolerance_40.json +++ b/org.hl7.fhir.convertors/src/test/resources/1_allergy_intolerance_40.json @@ -1,5 +1,6 @@ { "resourceType" : "AllergyIntolerance", + "id": "TKebKfLXzu6Sp.LY-IpvpmQB", "clinicalStatus" : { "coding" : [ { From f7b0e0303653a5367fca4f861c89fefe98fcf131 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 7 Sep 2020 13:05:11 +1000 Subject: [PATCH 11/21] Fix errors returned from version convertor --- .../org/hl7/fhir/convertors/VersionConvertor_40_50.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_40_50.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_40_50.java index 2121ad12e..973d8515a 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_40_50.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_40_50.java @@ -3742,7 +3742,7 @@ public class VersionConvertor_40_50 { return convertUsageContext((org.hl7.fhir.r4.model.UsageContext) src); if (src instanceof org.hl7.fhir.r4.model.ElementDefinition) return convertElementDefinition((org.hl7.fhir.r4.model.ElementDefinition) src); - throw new Error("Unknown type " + src.fhirType()); + throw new FHIRException("Unknown type " + src.fhirType()); } public static org.hl7.fhir.r4.model.Type convertType(org.hl7.fhir.r5.model.DataType src) throws FHIRException { @@ -3868,7 +3868,7 @@ public class VersionConvertor_40_50 { return convertUsageContext((org.hl7.fhir.r5.model.UsageContext) src); if (src instanceof org.hl7.fhir.r5.model.ElementDefinition) return convertElementDefinition((org.hl7.fhir.r5.model.ElementDefinition) src); - throw new Error("Unknown type " + src.fhirType()); + throw new FHIRException("Unknown type " + src.fhirType()); } protected static void copyDomainResource(org.hl7.fhir.r4.model.DomainResource src, org.hl7.fhir.r5.model.DomainResource tgt) throws FHIRException { @@ -4174,7 +4174,7 @@ public class VersionConvertor_40_50 { return VerificationResult40_50.convertVerificationResult((org.hl7.fhir.r4.model.VerificationResult) src); if (src instanceof org.hl7.fhir.r4.model.VisionPrescription) return VisionPrescription40_50.convertVisionPrescription((org.hl7.fhir.r4.model.VisionPrescription) src); - throw new Error("Unknown resource " + src.fhirType()); + throw new FHIRException("Unknown resource " + src.fhirType()); } public static org.hl7.fhir.r4.model.Resource convertResource(org.hl7.fhir.r5.model.Resource src) throws FHIRException { @@ -4441,7 +4441,7 @@ public class VersionConvertor_40_50 { return VerificationResult40_50.convertVerificationResult((org.hl7.fhir.r5.model.VerificationResult) src); if (src instanceof org.hl7.fhir.r5.model.VisionPrescription) return VisionPrescription40_50.convertVisionPrescription((org.hl7.fhir.r5.model.VisionPrescription) src); - throw new Error("Unknown resource " + src.fhirType()); + throw new FHIRException("Unknown resource " + src.fhirType()); } protected static org.hl7.fhir.r5.model.CodeType convertResourceEnum(org.hl7.fhir.r4.model.CodeType src) { From b47a1da05499375383b1c17b6805d3632b26fa0d Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 7 Sep 2020 13:06:43 +1000 Subject: [PATCH 12/21] Add check for UCUM annotations + add ValidationControl for hosting the validator in external processes --- .../src/main/resources/Messages.properties | 1 + .../hl7/fhir/validation/BaseValidator.java | 70 +++++++++++++++++-- .../hl7/fhir/validation/ValidationEngine.java | 25 ++++++- .../instance/InstanceValidator.java | 16 +++-- .../validation/tests/ValidationTestSuite.java | 10 ++- 5 files changed, 109 insertions(+), 13 deletions(-) diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index a72555b61..c4705c191 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -605,3 +605,4 @@ FHIRPATH_NUMERICAL_ONLY = Error evaluating FHIRPath expression: The function {0} FHIRPATH_DECIMAL_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on a decimal but found {1} FHIRPATH_FOCUS_PLURAL = Error evaluating FHIRPath expression: focus for {0} has more than one value REFERENCE_REF_SUSPICIOUS = The syntax of the reference ''{0}'' looks incorrect, and it should be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = UCUM Codes that contain human readable annotations like {0} can be misleading. Best Practice is not to use annotations in the UCUM code, and rather to make sure that Quantity.unit is correctly human readable \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index c35b966e4..9e1bd5dd3 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -2,7 +2,12 @@ package org.hl7.fhir.validation; import static org.apache.commons.lang3.StringUtils.isBlank; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; /* Copyright (c) 2011+, HL7, Inc. @@ -64,6 +69,8 @@ POSSIBILITY OF SUCH DAMAGE. */ import java.util.List; +import java.util.Map; + import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.elementmodel.Element; @@ -78,10 +85,28 @@ import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; import org.hl7.fhir.utilities.validation.ValidationMessage.Source; +import org.hl7.fhir.validation.BaseValidator.ValidationControl; import org.hl7.fhir.validation.instance.utils.IndexedElement; public class BaseValidator { + public class ValidationControl { + private boolean allowed; + private IssueSeverity level; + + public ValidationControl(boolean allowed, IssueSeverity level) { + super(); + this.allowed = allowed; + this.level = level; + } + public boolean isAllowed() { + return allowed; + } + public IssueSeverity getLevel() { + return level; + } + } + protected final String META = "meta"; protected final String ENTRY = "entry"; protected final String DOCUMENT = "document"; @@ -98,7 +123,16 @@ public class BaseValidator { protected Source source; protected IWorkerContext context; protected TimeTracker timeTracker = new TimeTracker(); - + + /** + * Use to control what validation the validator performs. + * Using this, you can turn particular kinds of validation on and off + * In addition, you can override the error | warning | hint level and make it a different level + * + * There is no way to do this using the command line validator; it's a service that is only + * offered when the validator is hosted in some other process + */ + private Map validationControl = new HashMap<>(); public BaseValidator(IWorkerContext context){ this.context = context; @@ -287,7 +321,10 @@ public class BaseValidator { protected boolean txRule(List errors, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { if (!thePass) { String message = context.formatMessage(theMessage, theMessageArguments); - errors.add(new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setTxLink(txLink)); + ValidationMessage vm = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setMessageId(theMessage); + if (checkMsgId(theMessage, vm)) { + errors.add(vm.setTxLink(txLink)); + } } return thePass; } @@ -415,10 +452,23 @@ public class BaseValidator { protected ValidationMessage addValidationMessage(List errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, Source theSource, String id) { ValidationMessage validationMessage = new ValidationMessage(theSource, type, line, col, path, msg, theSeverity).setMessageId(id); - errors.add(validationMessage); + if (checkMsgId(id, validationMessage)) { + errors.add(validationMessage); + } return validationMessage; } + public boolean checkMsgId(String id, ValidationMessage vm) { + if (id != null && validationControl.containsKey(id)) { + ValidationControl control = validationControl.get(id); + if (control.level != null) { + vm.setLevel(control.level); + } + return control.isAllowed(); + } + return true; + } + /** * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails * @@ -429,7 +479,10 @@ public class BaseValidator { protected boolean txWarning(List errors, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { if (!thePass) { String nmsg = context.formatMessage(msg, theMessageArguments); - errors.add(new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg)); + ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg); + if (checkMsgId(msg, vmsg)) { + errors.add(vmsg); + } } return thePass; @@ -567,7 +620,10 @@ public class BaseValidator { } protected void addValidationMessage(List errors, IssueType type, String path, String msg, String html, IssueSeverity theSeverity, String id) { - errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, theSeverity).setMessageId(id)); + ValidationMessage vm = new ValidationMessage(source, type, -1, -1, path, msg, html, theSeverity); + if (checkMsgId(id, vm)) { + errors.add(vm.setMessageId(id)); + } } /** @@ -802,5 +858,9 @@ public class BaseValidator { } } + public Map getValidationControl() { + return validationControl; + } + } \ No newline at end of file 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 f6cc13dcc..b6ca6b9f2 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 @@ -34,6 +34,7 @@ import org.hl7.fhir.r5.utils.*; import org.hl7.fhir.r5.utils.IResourceValidator.*; import org.hl7.fhir.r5.utils.StructureMapUtilities.ITransformerServices; import org.hl7.fhir.utilities.i18n.I18nConstants; +import org.hl7.fhir.validation.BaseValidator.ValidationControl; import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller; import org.hl7.fhir.validation.instance.InstanceValidator; import org.hl7.fhir.utilities.IniFile; @@ -309,6 +310,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst private List igs = new ArrayList<>(); private boolean showTimes; private List bundleValidationRules = new ArrayList<>(); + private Map validationControl = new HashMap<>(); private class AsteriskFilter implements FilenameFilter { String dir; @@ -1580,6 +1582,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst validator.setFetcher(this); validator.getImplementationGuides().addAll(igs); validator.getBundleValidationRules().addAll(bundleValidationRules); + validator.getValidationControl().putAll(validationControl ); return validator; } @@ -2391,9 +2394,27 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return pcm.packageExists(id, ver); } - public void loadPackage(String id, String ver) throws IOException, FHIRException { loadIg(id+(ver == null ? "" : "#"+ver), true); } - + + /** + * Systems that host the ValidationEngine can use this to control what validation the validator performs. + * + * Using this, you can turn particular kinds of validation on and off. In addition, you can override + * the error | warning | hint level and make it a different level. + * + * Each entry has + * * 'allowed': a boolean flag. if this is false, the Validator will not report the error. + * * 'level' : set to error, warning, information + * + * Entries are registered by ID, using the IDs in /org.hl7.fhir.utilities/src/main/resources/Messages.properties + * + * This feature is not supported by the validator CLI - and won't be. It's for systems hosting + * the validation framework in their own implementation context + */ + public Map getValidationControl() { + return validationControl; + } + } \ No newline at end of file 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 517b06b11..60ef546e1 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 @@ -491,17 +491,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return false; } - private void bpCheck(List errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) { + private void bpCheck(List errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message, Object... theMessageArguments) { if (bpWarnings != null) { switch (bpWarnings) { case Error: - rule(errors, invalid, line, col, literalPath, test, message); + rule(errors, invalid, line, col, literalPath, test, message, theMessageArguments); break; case Warning: - warning(errors, invalid, line, col, literalPath, test, message); + warning(errors, invalid, line, col, literalPath, test, message, theMessageArguments); break; case Hint: - hint(errors, invalid, line, col, literalPath, test, message); + hint(errors, invalid, line, col, literalPath, test, message, theMessageArguments); break; default: // do nothing break; @@ -2320,6 +2320,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (system != null || code != null ) { checkCodedElement(theErrors, thePath, element, theProfile, definition, false, false, theStack, code, system, unit); } + + if (code != null && "http://unitsofmeasure.org".equals(system)) { + int b = code.indexOf("{"); + int e = code.indexOf("}"); + if (b >= 0 && e > 0 && b < e) { + bpCheck(theErrors, IssueType.BUSINESSRULE, element.line(), element.col(), thePath, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS, code.substring(b, e+1)); + } + } } private void checkAttachment(List errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) { diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java index dfd6b8d99..84bb07d8e 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java @@ -36,6 +36,7 @@ import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule; import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy; @@ -204,6 +205,9 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour } else { val.setDebug(false); } + if (content.has("best-practice")) { + val.setBestPracticeWarningLevel(BestPracticeWarningLevel.valueOf(content.get("best-practice").getAsString())); + } if (content.has("examples")) { val.setAllowExamples(content.get("examples").getAsBoolean()); } else { @@ -367,10 +371,12 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour } if (!TestingUtilities.context(version).isNoTerminologyServer() || !focus.has("tx-dependent")) { Assert.assertEquals("Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("errorCount").getAsInt()) + " errors, but found " + Integer.toString(ec) + ".", java.get("errorCount").getAsInt(), ec); - if (java.has("warningCount")) + if (java.has("warningCount")) { Assert.assertEquals( "Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("warningCount").getAsInt()) + " warnings, but found " + Integer.toString(wc) + ".", java.get("warningCount").getAsInt(), wc); - if (java.has("infoCount")) + } + if (java.has("infoCount")) { Assert.assertEquals( "Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("infoCount").getAsInt()) + " hints, but found " + Integer.toString(hc) + ".", java.get("infoCount").getAsInt(), hc); + } } if (java.has("error-locations")) { JsonArray el = java.getAsJsonArray("error-locations"); From f2f53c7872c7c5fd9ccb09c7a27d1db9e94eff14 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 7 Sep 2020 13:07:21 +1000 Subject: [PATCH 13/21] FHIR Path fixes for reconciled tests --- .../org/hl7/fhir/r5/utils/FHIRPathEngine.java | 103 ++++++++++++++---- .../org/hl7/fhir/r5/test/FHIRPathTests.java | 3 +- 2 files changed, 82 insertions(+), 24 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index e16776f40..e7a7f33a2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -795,6 +795,10 @@ public class FHIRPathEngine { return item.primitiveValue(); } else if (item instanceof Quantity) { Quantity q = (Quantity) item; + if (q.hasUnit() && Utilities.existsInList(q.getUnit(), "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds") + && (!q.hasSystem() || q.getSystem().equals("http://unitsofmeasure.org"))) { + return q.getValue().toPlainString()+" "+q.getUnit(); + } if (q.getSystem().equals("http://unitsofmeasure.org")) { String u = "'"+q.getCode()+"'"; return q.getValue().toPlainString()+" "+u; @@ -954,12 +958,14 @@ public class FHIRPathEngine { if (!isString && !lexer.done() && (result.getConstant() instanceof IntegerType || result.getConstant() instanceof DecimalType) && (lexer.isStringConstant() || lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds"))) { // it's a quantity String ucum = null; + String unit = null; if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) { String s = lexer.take(); + unit = s; if (s.equals("year") || s.equals("years")) { - ucum = "a"; + // this is not the UCUM year } else if (s.equals("month") || s.equals("months")) { - ucum = "mo"; + // this is not the UCUM month } else if (s.equals("week") || s.equals("weeks")) { ucum = "wk"; } else if (s.equals("day") || s.equals("days")) { @@ -976,7 +982,7 @@ public class FHIRPathEngine { } else { ucum = lexer.readConstant("units"); } - result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setSystem("http://unitsofmeasure.org").setCode(ucum)); + result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setUnit(unit).setSystem(ucum == null ? null : "http://unitsofmeasure.org").setCode(ucum)); } result.setEnd(lexer.getCurrentLocation()); } else if ("(".equals(lexer.getCurrent())) { @@ -1934,18 +1940,50 @@ public class FHIRPathEngine { } } - private boolean qtyEqual(Quantity left, Quantity right) { + private Boolean qtyEqual(Quantity left, Quantity right) { + if (!left.hasValue() && !right.hasValue()) { + return true; + } + if (!left.hasValue() || !right.hasValue()) { + return null; + } if (worker.getUcumService() != null) { - DecimalType dl = qtyToCanonical(left); - DecimalType dr = qtyToCanonical(right); + Pair dl = qtyToCanonicalPair(left); + Pair dr = qtyToCanonicalPair(right); if (dl != null && dr != null) { - return doEquals(dl, dr); + if (dl.getCode().equals(dr.getCode())) { + return doEquals(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal())); + } else { + return false; + } } } - return left.equals(right); + if (left.hasCode() || right.hasCode()) { + if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) { + return null; + } + } else if (!left.hasUnit() || right.hasUnit()) { + if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) { + return null; + } + } + return doEquals(new DecimalType(left.getValue()), new DecimalType(right.getValue())); } - private DecimalType qtyToCanonical(Quantity q) { + private Pair qtyToCanonicalPair(Quantity q) { + if (!"http://unitsofmeasure.org".equals(q.getSystem())) { + return null; + } + try { + Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode()); + Pair c = worker.getUcumService().getCanonicalForm(p); + return c; + } catch (UcumException e) { + return null; + } + } + + private DecimalType qtyToCanonicalDecimal(Quantity q) { if (!"http://unitsofmeasure.org".equals(q.getSystem())) { return null; } @@ -1975,15 +2013,34 @@ public class FHIRPathEngine { } - private boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException { + private Boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException { + if (!left.hasValue() && !right.hasValue()) { + return true; + } + if (!left.hasValue() || !right.hasValue()) { + return null; + } if (worker.getUcumService() != null) { - DecimalType dl = qtyToCanonical(left); - DecimalType dr = qtyToCanonical(right); + Pair dl = qtyToCanonicalPair(left); + Pair dr = qtyToCanonicalPair(right); if (dl != null && dr != null) { - return doEquivalent(dl, dr); + if (dl.getCode().equals(dr.getCode())) { + return doEquivalent(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal())); + } else { + return false; + } } } - return left.equals(right); + if (left.hasCode() || right.hasCode()) { + if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) { + return null; + } + } else if (!left.hasUnit() || right.hasUnit()) { + if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) { + return null; + } + } + return doEquivalent(new DecimalType(left.getValue()), new DecimalType(right.getValue())); } @@ -2072,9 +2129,9 @@ public class FHIRPathEngine { return makeBoolean(false); } else { List dl = new ArrayList(); - dl.add(qtyToCanonical((Quantity) left.get(0))); + dl.add(qtyToCanonicalDecimal((Quantity) left.get(0))); List dr = new ArrayList(); - dr.add(qtyToCanonical((Quantity) right.get(0))); + dr.add(qtyToCanonicalDecimal((Quantity) right.get(0))); return opLessThan(dl, dr); } } @@ -2119,9 +2176,9 @@ public class FHIRPathEngine { return makeBoolean(false); } else { List dl = new ArrayList(); - dl.add(qtyToCanonical((Quantity) left.get(0))); + dl.add(qtyToCanonicalDecimal((Quantity) left.get(0))); List dr = new ArrayList(); - dr.add(qtyToCanonical((Quantity) right.get(0))); + dr.add(qtyToCanonicalDecimal((Quantity) right.get(0))); return opGreater(dl, dr); } } @@ -2169,9 +2226,9 @@ public class FHIRPathEngine { return makeBoolean(false); } else { List dl = new ArrayList(); - dl.add(qtyToCanonical((Quantity) left.get(0))); + dl.add(qtyToCanonicalDecimal((Quantity) left.get(0))); List dr = new ArrayList(); - dr.add(qtyToCanonical((Quantity) right.get(0))); + dr.add(qtyToCanonicalDecimal((Quantity) right.get(0))); return opLessOrEqual(dl, dr); } } @@ -2217,9 +2274,9 @@ public class FHIRPathEngine { return makeBoolean(false); } else { List dl = new ArrayList(); - dl.add(qtyToCanonical((Quantity) left.get(0))); + dl.add(qtyToCanonicalDecimal((Quantity) left.get(0))); List dr = new ArrayList(); - dr.add(qtyToCanonical((Quantity) right.get(0))); + dr.add(qtyToCanonicalDecimal((Quantity) right.get(0))); return opGreaterOrEqual(dl, dr); } } @@ -4857,7 +4914,7 @@ public class FHIRPathEngine { if (s.equals("year") || s.equals("years")) { return Quantity.fromUcum(v, "a"); } else if (s.equals("month") || s.equals("months")) { - return Quantity.fromUcum(v, "mo"); + return Quantity.fromUcum(v, "mo_s"); } else if (s.equals("week") || s.equals("weeks")) { return Quantity.fromUcum(v, "wk"); } else if (s.equals("day") || s.equals("days")) { diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java index ab8a5ffa6..e271d62f1 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java @@ -145,7 +145,7 @@ public class FHIRPathTests { public void test(String name, Element test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException { // Setting timezone for this test. Grahame is in UTC+11, Travis is in GMT, and I'm here in Toronto, Canada with // all my time based tests failing locally... - TimeZone.setDefault(TimeZone.getTimeZone("UTC+1100")); + TimeZone.setDefault(TimeZone.getTimeZone("UTC+1100")); fp.setHostServices(new FHIRPathTestEvaluationServices()); String input = test.getAttribute("inputfile"); @@ -178,6 +178,7 @@ public class FHIRPathTests { outcome.clear(); outcome.add(new BooleanType(ok)); } + System.out.println(name); if (fp.hasLog()) { System.out.println(name); System.out.println(fp.takeLog()); From 227bccbb35e9dacb5d356d132aee9c5b3097bfc1 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 7 Sep 2020 13:08:21 +1000 Subject: [PATCH 14/21] fix for empty markdown + rendering improvements for Bundles --- .../hl7/fhir/r5/renderers/BundleRenderer.java | 3 +-- .../org/hl7/fhir/r5/renderers/DataRenderer.java | 16 ++++++++++++++++ .../hl7/fhir/utilities/MarkDownProcessor.java | 6 ++++++ .../hl7/fhir/utilities/i18n/I18nConstants.java | 3 ++- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java index 614b54695..735b139dd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java @@ -295,8 +295,7 @@ public class BundleRenderer extends ResourceRenderer { try { xn = rr.build(be.getResource()); } catch (Exception e) { - xn = new XhtmlNode(); - xn.para().b().tx("Exception generating narrative: "+e.getMessage()); + xn = makeExceptionXhtml(e, "generating narrative"); } } root.blockquote().getChildNodes().addAll(checkInternalLinks(b, xn.getChildNodes())); 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 b5dce979b..16322b195 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 @@ -58,6 +58,7 @@ import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationOptions; +import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xhtml.XhtmlParser; @@ -1021,6 +1022,21 @@ public class DataRenderer extends Renderer { } + public XhtmlNode makeExceptionXhtml(Exception e, String function) { + XhtmlNode xn; + xn = new XhtmlNode(NodeType.Element, "div"); + xn.para().b().tx("Exception "+function+": "+e.getMessage()).addComment(getStackTrace(e)); + return xn; + } + private String getStackTrace(Exception e) { + StringBuilder b = new StringBuilder(); + b.append("\r\n"); + for (StackTraceElement t : e.getStackTrace()) { + b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); + b.append("\r\n"); + } + return b.toString(); + } } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MarkDownProcessor.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MarkDownProcessor.java index c733b5548..d85ddc14a 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MarkDownProcessor.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MarkDownProcessor.java @@ -56,6 +56,12 @@ public class MarkDownProcessor { public String process(String source, String context) { + if (source == null) { + return null; + } + if ("".equals(source)) { + return ""; + } switch (dialect) { case DARING_FIREBALL : return Processor.process(source); case COMMON_MARK : return processCommonMark(source); 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 d258606dc..93dd445ad 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 @@ -161,7 +161,7 @@ public class I18nConstants { public static final String FHIRPATH_UNKNOWN_CONSTANT = "FHIRPATH_UNKNOWN_CONSTANT"; public static final String FHIRPATH_UNKNOWN_CONTEXT = "FHIRPATH_UNKNOWN_CONTEXT"; public static final String FHIRPATH_UNKNOWN_CONTEXT_ELEMENT = "FHIRPATH_UNKNOWN_CONTEXT_ELEMENT"; - public static final String FHIRPATH_UNKNOWN_NAME = "FHIRPATH_UNWKNOWN_NAME"; + public static final String FHIRPATH_UNKNOWN_NAME = "FHIRPATH_UNKNOWN_NAME"; public static final String FHIRPATH_WRONG_PARAM_TYPE = "FHIRPATH_WRONG_PARAM_TYPE"; public static final String FIXED_TYPE_CHECKS_DT_ADDRESS_LINE = "Fixed_Type_Checks_DT_Address_Line"; public static final String FIXED_TYPE_CHECKS_DT_NAME_FAMILY = "Fixed_Type_Checks_DT_Name_Family"; @@ -466,6 +466,7 @@ public class I18nConstants { public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX = "Type_Specific_Checks_DT_Primitive_Regex"; public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT = "Type_Specific_Checks_DT_Primitive_ValueExt"; public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_WS = "Type_Specific_Checks_DT_Primitive_WS"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = "TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS"; public static final String TYPE_SPECIFIC_CHECKS_DT_STRING_LENGTH = "Type_Specific_Checks_DT_String_Length"; public static final String TYPE_SPECIFIC_CHECKS_DT_STRING_WS = "Type_Specific_Checks_DT_String_WS"; public static final String TYPE_SPECIFIC_CHECKS_DT_TIME_VALID = "Type_Specific_Checks_DT_Time_Valid"; From 4c245ea3e311cbafef5891b9b13e9fd643155991 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 8 Sep 2020 12:43:56 +1000 Subject: [PATCH 15/21] Fix issues with generating invalid html --- .../r5/renderers/ProfileDrivenRenderer.java | 100 +++++++++++------- 1 file changed, 62 insertions(+), 38 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 88870997d..9f3ebdbdd 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 @@ -41,6 +41,7 @@ import org.hl7.fhir.r5.model.InstantType; import org.hl7.fhir.r5.model.Meta; import org.hl7.fhir.r5.model.Narrative; import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; +import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r5.model.Period; import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.r5.model.Property; @@ -320,6 +321,12 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } else { x.addText("??"); } + } else if (e instanceof org.hl7.fhir.r5.model.Integer64Type) { + if (((org.hl7.fhir.r5.model.Integer64Type) e).hasValue()) { + x.addText(Long.toString(((org.hl7.fhir.r5.model.Integer64Type) e).getValue())); + } else { + x.addText("??"); + } } else if (e instanceof org.hl7.fhir.r5.model.DecimalType) { x.addText(((org.hl7.fhir.r5.model.DecimalType) e).getValue().toString()); } else if (e instanceof HumanName) { @@ -387,12 +394,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } else if (e instanceof ElementDefinition) { x.tx("todo-bundle"); } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) { - StructureDefinition sd = getContext().getWorker().fetchTypeDefinition(e.fhirType()); - if (sd == null) - throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet, and no structure found"); - else - generateByProfile(res, sd, ew, sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep(), - getChildrenForPath(sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep().getPath()), x, e.fhirType(), showCodeDetails, indent + 1); + throw new NotImplementedException("type "+e.getClass().getName()+" not handled - should not be here"); } } @@ -553,12 +555,28 @@ public class ProfileDrivenRenderer extends ResourceRenderer { private boolean isPrimitive(ElementDefinition e) { //we can tell if e is a primitive because it has types - if (e.getType().isEmpty()) + if (e.getType().isEmpty()) { return false; - if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) + } + if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) { return false; - return true; - // return !e.getType().isEmpty() + } + if (e.getType().size() > 1) { + return true; + } + StructureDefinition sd = context.getWorker().fetchTypeDefinition(e.getTypeFirstRep().getCode()); + if (sd != null) { + if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { + return true; + } + if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) { + if (Utilities.existsInList(e.getTypeFirstRep().getCode(), "Extension", "CodeableConcept", "Coding", "Annotation", "Identifier", "HumanName", "SampledData", + "Address", "ContactPoint", "ContactDetail", "Timing", "Range", "Quantity", "Ratio", "Period", "Reference")) { + return true; + } + } + } + return false; } private boolean isBase(String code) { @@ -646,31 +664,32 @@ public class ProfileDrivenRenderer extends ResourceRenderer { filterGrandChildren(grandChildren, path+"."+p.getName(), p); if (p.getValues().size() > 0) { if (isPrimitive(child)) { - XhtmlNode para = x.para(); - String name = p.getName(); - if (name.endsWith("[x]")) - name = name.substring(0, name.length() - 3); - if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { - para.b().addText(name); - para.tx(": "); - if (renderAsList(child) && p.getValues().size() > 1) { - XhtmlNode list = x.ul(); - for (BaseWrapper v : p.getValues()) - renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent); - } else { - boolean first = true; - for (BaseWrapper v : p.getValues()) { - if (first) - first = false; - else - para.tx(", "); - renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); - } - } - } - } else if (canDoTable(path, p, grandChildren)) { + XhtmlNode para = x.isPara() ? para = x : x.para(); + String name = p.getName(); + if (name.endsWith("[x]")) + name = name.substring(0, name.length() - 3); + if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { + para.b().addText(name); + para.tx(": "); + if (renderAsList(child) && p.getValues().size() > 1) { + XhtmlNode list = x.ul(); + for (BaseWrapper v : p.getValues()) + renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent); + } else { + boolean first = true; + for (BaseWrapper v : p.getValues()) { + if (first) { + first = false; + } else { + para.tx(", "); + } + renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent); + } + } + } + } else if (canDoTable(path, p, grandChildren, x)) { x.addTag(getHeader()).addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); - XhtmlNode tbl = x.table( "grid"); + XhtmlNode tbl = x.table("grid"); XhtmlNode tr = tbl.tr(); tr.td().tx("-"); // work around problem with empty table rows addColumnHeadings(tr, grandChildren); @@ -737,10 +756,13 @@ public class ProfileDrivenRenderer extends ResourceRenderer { return res; } - private boolean canDoTable(String path, PropertyWrapper p, List grandChildren) { + private boolean canDoTable(String path, PropertyWrapper p, List grandChildren, XhtmlNode x) { if (isExtension(p)) { return false; } + if (x.getName().equals("p")) { + return false; + } for (ElementDefinition e : grandChildren) { List values = getValues(path, p, e); if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e)) @@ -830,15 +852,17 @@ public class ProfileDrivenRenderer extends ResourceRenderer { getContext().getWorker().cacheResource(ed); } } - if (p.getName().equals("modifierExtension") && ed == null) + if (p.getName().equals("modifierExtension") && ed == null) { throw new DefinitionException("Unknown modifier extension "+url); + } PropertyWrapper pe = map.get(p.getName()+"["+url+"]"); if (pe == null) { if (ed == null) { - if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) + if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) { throw new DefinitionException("unknown extension "+url); + } // System.out.println("unknown extension "+url); - pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), ed.getSnapshot().getElementFirstRep()); + pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), null); } else { ElementDefinition def = ed.getSnapshot().getElement().get(0); pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex), ed.getSnapshot().getElementFirstRep()); From 4dd89160dd5ced925bbc525aa23cb1f60a36e108 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 8 Sep 2020 12:45:13 +1000 Subject: [PATCH 16/21] Add validation for html block elements inside paragraphs --- .../r5/test/NarrativeGenerationTests.java | 2 +- .../fhir/utilities/i18n/I18nConstants.java | 1 + .../src/main/resources/Messages.properties | 3 ++- .../instance/InstanceValidator.java | 19 +++++++++++-------- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java index 9011cd929..6bb55c3ff 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java @@ -152,7 +152,7 @@ public class NarrativeGenerationTests { x = RendererFactory.factory(source, rc).render(new ElementWrappers.ResourceWrapperMetaElement(rc, e)); target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html")); - output = HEADER+new XhtmlComposer(true).compose(x)+FOOTER; + output = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER; TextFile.stringToFile(output, TestingUtilities.tempFile("narrative", test.getId() + "-meta.output.html")); Assertions.assertTrue(output.equals(target), "Output does not match expected (meta)"); } 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 93dd445ad..99f9fe519 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 @@ -579,6 +579,7 @@ public class I18nConstants { public static final String XHTML_XHTML_ATTRIBUTE_ILLEGAL = "XHTML_XHTML_Attribute_Illegal"; public static final String XHTML_XHTML_DOCTYPE_ILLEGAL = "XHTML_XHTML_DOCTYPE_ILLEGAL"; public static final String XHTML_XHTML_ELEMENT_ILLEGAL = "XHTML_XHTML_Element_Illegal"; + public static final String XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA = "XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA"; public static final String XHTML_XHTML_NAME_INVALID = "XHTML_XHTML_Name_Invalid"; public static final String XHTML_XHTML_NS_INVALID = "XHTML_XHTML_NS_InValid"; public static final String XML_ATTR_VALUE_INVALID = "xml_attr_value_invalid"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index c4705c191..bde0e0936 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -605,4 +605,5 @@ FHIRPATH_NUMERICAL_ONLY = Error evaluating FHIRPath expression: The function {0} FHIRPATH_DECIMAL_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on a decimal but found {1} FHIRPATH_FOCUS_PLURAL = Error evaluating FHIRPath expression: focus for {0} has more than one value REFERENCE_REF_SUSPICIOUS = The syntax of the reference ''{0}'' looks incorrect, and it should be checked -TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = UCUM Codes that contain human readable annotations like {0} can be misleading. Best Practice is not to use annotations in the UCUM code, and rather to make sure that Quantity.unit is correctly human readable \ No newline at end of file +TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = UCUM Codes that contain human readable annotations like {0} can be misleading. Best Practice is not to use annotations in the UCUM code, and rather to make sure that Quantity.unit is correctly human readable +XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA = Illegal element name inside in a paragraph in the XHTML (''{0}'') 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 60ef546e1..465e0835b 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 @@ -2070,7 +2070,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat checkInnerNS(errors, e, path, xhtml.getChildNodes()); rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, ns); // check that no illegal elements and attributes have been used - checkInnerNames(errors, e, path, xhtml.getChildNodes()); + checkInnerNames(errors, e, path, xhtml.getChildNodes(), false); checkUrls(errors, e, path, xhtml.getChildNodes()); } } @@ -2171,7 +2171,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat "http://hl7.org/fhirpath/System.Decimal", "http://hl7.org/fhirpath/System.Date", "http://hl7.org/fhirpath/System.Time", "http://hl7.org/fhirpath/System.DateTime", "http://hl7.org/fhirpath/System.Quantity"); } - private void checkInnerNames(List errors, Element e, String path, List list) { + private void checkInnerNames(List errors, Element e, String path, List list, boolean inPara) { for (XhtmlNode node : list) { if (node.getNodeType() == NodeType.Comment) { rule(errors, IssueType.INVALID, e.line(), e.col(), path, !node.getContent().startsWith("DOCTYPE"), I18nConstants.XHTML_XHTML_DOCTYPE_ILLEGAL); @@ -2181,9 +2181,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", - "code", "samp", "img", "map", "area" - - ), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName()); + "code", "samp", "img", "map", "area"), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName()); + for (String an : node.getAttributes().keySet()) { boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, "title", "style", "class", ID, "lang", "xml:lang", "dir", "accesskey", "tabindex", @@ -2195,11 +2194,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap" - ); - if (!ok) + ); + if (!ok) { rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_ILLEGAL, an, node.getName()); + } } - checkInnerNames(errors, e, path, node.getChildNodes()); + + rule(errors, IssueType.INVALID, e.line(), e.col(), path, !(inPara && Utilities.existsInList(node.getName(), "div", "blockquote", "table", "ol", "ul", "p")) , I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA, node.getName()); + + checkInnerNames(errors, e, path, node.getChildNodes(), inPara || "p".equals(node.getName())); } } } From 41d40e7f94ba51c6a61ed14fad3d3923e01b629c Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 8 Sep 2020 12:45:58 +1000 Subject: [PATCH 17/21] More validation for HTML structure --- .../hl7/fhir/utilities/xhtml/XhtmlNode.java | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java index 936bb7cdd..d501ff789 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java @@ -41,7 +41,10 @@ import java.util.Map; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseXhtml; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.i18n.I18nConstants; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; +import ca.uhn.fhir.model.api.annotation.ChildOrder; import ca.uhn.fhir.model.primitive.XhtmlDt; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -137,11 +140,53 @@ public class XhtmlNode implements IBaseXhtml { return this; } + public void validate(List errors, String path, boolean inResource, boolean inPara) { + if (nodeType == NodeType.Element || nodeType == NodeType.Document) { + path = Utilities.noString(path) ? name : path+"/"+name; + if (inResource) { + if (!Utilities.existsInList(name, "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", + "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", + "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", + "code", "samp", "img", "map", "area")) { + errors.add("Error at "+path+": Found "+name+" in a resource"); + } + for (String an : attributes.keySet()) { + boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, + "title", "style", "class", "ID", "lang", "xml:lang", "dir", "accesskey", "tabindex", + // tables + "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || + Utilities.existsInList(name + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite", + "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src", + "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", + "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", + "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap" + ); + if (!ok) + errors.add("Error at "+path+": Found attribute "+name+"."+an+" in a resource"); + } + } + if (inPara && Utilities.existsInList(name, "div", "blockquote", "table", "ol", "ul", "p")) { + errors.add("Error at "+path+": Found "+name+" inside an html paragraph"); + } + + if (childNodes != null) { + if ("p".equals(name)) { + inPara = true; + } + for (XhtmlNode child : childNodes) { + child.validate(errors, path, inResource, inPara); + } + } + } + } + public XhtmlNode addTag(String name) { - if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) + if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) { throw new Error("Wrong node type - node is "+nodeType.toString()+" ('"+getName()+"/"+getContent()+"')"); + } + XhtmlNode node = new XhtmlNode(NodeType.Element); node.setName(name); childNodes.add(node); @@ -188,7 +233,6 @@ public class XhtmlNode implements IBaseXhtml { childNodes.add(node); return node; } - public XhtmlNode addText(String content) { if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) @@ -747,6 +791,11 @@ public class XhtmlNode implements IBaseXhtml { } + public boolean isPara() { + return "p".equals(name); + } + + From 1ebd74f0d0575bec1a44812e79ea00c07fb42e32 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 8 Sep 2020 14:01:33 +1000 Subject: [PATCH 18/21] Add release notes --- RELEASE_NOTES.md | 11 +++++++++++ .../org/hl7/fhir/utilities/xhtml/XhtmlParser.java | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e69de29bb..242e1ecb1 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -0,0 +1,11 @@ +Validator Fixes: +* Check that there are no nested block HTML elements in an HTML paragraph +* create a warning for UCUM annotations + +Other Changes: +* Fix some issues when converting Allergy Intolerance 3 <-> 4 +* Fix issues generating invsalid HTML +* Fix up FHIRPath to pass latest test cases +* Bundle rendering improvements +* Give code interface for control over validation rules +* add support for rendering choice groups (CDA) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java index 9ee402f73..af088b66b 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java @@ -536,8 +536,9 @@ private boolean elementIsOk(String name) throws FHIRFormatError { return; else { - if (mustBeWellFormed) + if (mustBeWellFormed) { throw new FHIRFormatError("Malformed XHTML: Found \"\" expecting \"\""+descLoc()); + } for (int i = parents.size() - 1; i >= 0; i--) { if (parents.get(i).getName().equals(n)) From 1822950354f28418bbd2e2aa779b969e07006aa9 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 8 Sep 2020 14:37:36 +1000 Subject: [PATCH 19/21] Fix teste case dependency --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 611b9deb9..fa85b8f9f 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 5.1.0 - 1.1.36-SNAPSHOT + 1.1.36 5.6.2 3.0.0-M4 0.8.5 From a92daa201790dda92c3299c877bbf0d17445623e Mon Sep 17 00:00:00 2001 From: markiantorno Date: Tue, 8 Sep 2020 05:13:12 +0000 Subject: [PATCH 20/21] Release: v5.1.8 Validator Fixes: * Check that there are no nested block HTML elements in an HTML paragraph * create a warning for UCUM annotations Other Changes: * Fix some issues when converting Allergy Intolerance 3 <-> 4 * Fix issues generating invsalid HTML * Fix up FHIRPath to pass latest test cases * Bundle rendering improvements * Give code interface for control over validation rules * add support for rendering choice groups (CDA) ***NO_CI*** --- org.hl7.fhir.convertors/pom.xml | 2 +- org.hl7.fhir.dstu2/pom.xml | 2 +- org.hl7.fhir.dstu2016may/pom.xml | 2 +- org.hl7.fhir.dstu3/pom.xml | 2 +- org.hl7.fhir.r4/pom.xml | 2 +- org.hl7.fhir.r5/pom.xml | 2 +- org.hl7.fhir.report/pom.xml | 2 +- org.hl7.fhir.utilities/pom.xml | 2 +- org.hl7.fhir.validation.cli/pom.xml | 2 +- org.hl7.fhir.validation/pom.xml | 2 +- pom.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index d8b256b28..e078513d6 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index 6b134939b..696701fcc 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index 7a19f507d..ab9c2ff6d 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index 4940d5e2d..e6bba2a0c 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index 3f18c1f73..eedba3b2a 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 522e24573..ab38d1de2 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index ff16d27f0..e4075c742 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index e15d7f239..abaef286e 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index f130a98f5..fa50fdc5a 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index 191a5e695..c217518c4 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8-SNAPSHOT + 5.1.8 ../pom.xml diff --git a/pom.xml b/pom.xml index fa85b8f9f..ca7e290aa 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ each other. It is fine to bump the point version of this POM without affecting HAPI FHIR. --> - 5.1.8-SNAPSHOT + 5.1.8 5.1.0 From a92876ab4a2e1a4ea188cfcf7900434bead0233f Mon Sep 17 00:00:00 2001 From: markiantorno Date: Tue, 8 Sep 2020 05:28:46 +0000 Subject: [PATCH 21/21] Updating version to: 5.1.9-SNAPSHOT and incrementing test cases dependency. --- RELEASE_NOTES.md | 11 ----------- org.hl7.fhir.convertors/pom.xml | 2 +- org.hl7.fhir.dstu2/pom.xml | 2 +- org.hl7.fhir.dstu2016may/pom.xml | 2 +- org.hl7.fhir.dstu3/pom.xml | 2 +- org.hl7.fhir.r4/pom.xml | 2 +- org.hl7.fhir.r5/pom.xml | 2 +- org.hl7.fhir.report/pom.xml | 2 +- org.hl7.fhir.utilities/pom.xml | 2 +- org.hl7.fhir.validation.cli/pom.xml | 2 +- org.hl7.fhir.validation/pom.xml | 2 +- pom.xml | 2 +- 12 files changed, 11 insertions(+), 22 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 242e1ecb1..e69de29bb 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,11 +0,0 @@ -Validator Fixes: -* Check that there are no nested block HTML elements in an HTML paragraph -* create a warning for UCUM annotations - -Other Changes: -* Fix some issues when converting Allergy Intolerance 3 <-> 4 -* Fix issues generating invsalid HTML -* Fix up FHIRPath to pass latest test cases -* Bundle rendering improvements -* Give code interface for control over validation rules -* add support for rendering choice groups (CDA) diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index e078513d6..7c32845f6 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index 696701fcc..552667954 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index ab9c2ff6d..7f4121239 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index e6bba2a0c..9b5407dae 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index eedba3b2a..7f0a3cf91 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index ab38d1de2..09f398ce4 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index e4075c742..e4e952637 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index abaef286e..a97aa598a 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index fa50fdc5a..4a660b2bf 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index c217518c4..bb573f077 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.1.8 + 5.1.9-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index ca7e290aa..75b5d969b 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ each other. It is fine to bump the point version of this POM without affecting HAPI FHIR. --> - 5.1.8 + 5.1.9-SNAPSHOT 5.1.0