From eacffd11df4ee0234ce4d922943394a704fbabbf Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 17 Aug 2023 11:14:34 +1000 Subject: [PATCH 01/13] Add support for external strings for tx tests --- .../fhir/r5/test/utils/CompareUtilities.java | 171 +++++++++++------- .../org/hl7/fhir/r5/test/ParsingTests.java | 2 +- .../r5/test/utils/CompareUtilitiesTests.java | 2 +- .../utilities/json/model/JsonPrimitive.java | 4 + .../validation/cli/tasks/TxTestsTask.java | 15 +- .../hl7/fhir/validation/cli/utils/Params.java | 1 + .../hl7/fhir/validation/special/TxTester.java | 20 +- .../comparison/tests/ComparisonTests.java | 93 +++++++++- .../fhir/r5/test/StructureMappingTests.java | 2 +- .../ExternalTerminologyServiceTests.java | 8 +- .../tests/TerminologyServiceTests.java | 20 +- 11 files changed, 248 insertions(+), 90 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java index 6eaa0dd6d..f1c27c413 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java @@ -2,20 +2,22 @@ package org.hl7.fhir.r5.test.utils; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.utilities.*; import org.hl7.fhir.utilities.json.JsonUtilities; +import org.hl7.fhir.utilities.json.model.JsonArray; +import org.hl7.fhir.utilities.json.model.JsonElement; +import org.hl7.fhir.utilities.json.model.JsonNull; +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.utilities.json.model.JsonPrimitive; +import org.hl7.fhir.utilities.json.model.JsonProperty; +import org.hl7.fhir.utilities.json.parser.JsonParser; import org.hl7.fhir.utilities.settings.FhirSettings; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; import org.hl7.fhir.utilities.tests.BaseTestingUtilities; import javax.xml.parsers.DocumentBuilder; @@ -28,21 +30,52 @@ import java.util.Map; public class CompareUtilities extends BaseTestingUtilities { private static final boolean SHOW_DIFF = true; - - public static String createNotEqualMessage(final String message, final String expected, final String actual) { + private JsonObject externals; + + public String createNotEqualMessage(final String message, final String expected, final String actual) { return new StringBuilder() .append(message).append('\n') - .append("Expected :").append(expected).append('\n') - .append("Actual :").append(actual).toString(); + .append("Expected :").append(presentExpected(expected)).append('\n') + .append("Actual :").append("\""+actual+"\"").toString(); + } + + private String presentExpected(String expected) { + if (expected.startsWith("$") && expected.endsWith("$")) { + if (expected.startsWith("$choice:")) { + return "Contains one of "+readChoices(8, expected).toString(); + } else if (expected.startsWith("$fragments:")) { + List fragments = readChoices(11, expected); + return "Contains all of "+fragments.toString(); + } else if (expected.startsWith("$external:")) { + String[] cmd = expected.substring(1, expected.length() - 1).split("\\:"); + if (externals != null) { + String s = externals.asString(cmd[1]); + return "\""+s+"\" (Ext)"; + } else { + return "Contains \""+cmd[2]+"\""; + } + } else { + switch (expected) { + case "$$" : return "$$"; + case "$instant$": return "\"An Instant\""; + case "$uuid$": return "\"A Uuid\""; + default: return "Unhandled template: "+expected; + } + } + } else { + return "\""+expected+"\""; + } } public static String checkXMLIsSame(InputStream expected, InputStream actual) throws Exception { - String result = compareXml(expected, actual); + CompareUtilities self = new CompareUtilities(); + String result = self.compareXml(expected, actual); return result; } public static String checkXMLIsSame(String expected, String actual) throws Exception { - String result = compareXml(expected, actual); + CompareUtilities self = new CompareUtilities(); + String result = self.compareXml(expected, actual); if (result != null && SHOW_DIFF) { String diff = getDiffTool(); if (diff != null && new File(diff).exists() || Utilities.isToken(diff)) { @@ -52,7 +85,7 @@ public class CompareUtilities extends BaseTestingUtilities { return result; } - private static String getDiffTool() throws IOException { + private static String getDiffTool() throws IOException { if (FhirSettings.hasDiffToolPath()) { return FhirSettings.getDiffToolPath(); } else if (System.getenv("ProgramFiles") != null) { @@ -62,15 +95,15 @@ public class CompareUtilities extends BaseTestingUtilities { } } - private static String compareXml(InputStream expected, InputStream actual) throws Exception { + private String compareXml(InputStream expected, InputStream actual) throws Exception { return compareElements("", loadXml(expected).getDocumentElement(), loadXml(actual).getDocumentElement()); } - private static String compareXml(String expected, String actual) throws Exception { + private String compareXml(String expected, String actual) throws Exception { return compareElements("", loadXml(expected).getDocumentElement(), loadXml(actual).getDocumentElement()); } - private static String compareElements(String path, Element expectedElement, Element actualElement) { + private String compareElements(String path, Element expectedElement, Element actualElement) { if (!namespacesMatch(expectedElement.getNamespaceURI(), actualElement.getNamespaceURI())) return createNotEqualMessage("Namespaces differ at " + path, expectedElement.getNamespaceURI(), actualElement.getNamespaceURI()); if (!expectedElement.getLocalName().equals(actualElement.getLocalName())) @@ -109,18 +142,18 @@ public class CompareUtilities extends BaseTestingUtilities { return null; } - private static boolean namespacesMatch(String ns1, String ns2) { + private boolean namespacesMatch(String ns1, String ns2) { return ns1 == null ? ns2 == null : ns1.equals(ns2); } - private static Object normalise(String text) { + private String normalise(String text) { String result = text.trim().replace('\r', ' ').replace('\n', ' ').replace('\t', ' '); while (result.contains(" ")) result = result.replace(" ", " "); return result; } - private static String compareAttributes(String path, NamedNodeMap expected, NamedNodeMap actual) { + private String compareAttributes(String path, NamedNodeMap expected, NamedNodeMap actual) { for (int i = 0; i < expected.getLength(); i++) { Node expectedNode = expected.item(i); @@ -140,7 +173,7 @@ public class CompareUtilities extends BaseTestingUtilities { return null; } - private static boolean sameBytes(byte[] b1, byte[] b2) { + private boolean sameBytes(byte[] b1, byte[] b2) { if (b1.length == 0 || b2.length == 0) return false; if (b1.length != b2.length) @@ -151,21 +184,21 @@ public class CompareUtilities extends BaseTestingUtilities { return true; } - private static byte[] unBase64(String text) { + private byte[] unBase64(String text) { return Base64.decodeBase64(text); } - private static Node skipBlankText(Node node) { + private Node skipBlankText(Node node) { while (node != null && (((node.getNodeType() == Node.TEXT_NODE) && StringUtils.isWhitespace(node.getTextContent())) || (node.getNodeType() == Node.COMMENT_NODE))) node = node.getNextSibling(); return node; } - private static Document loadXml(String fn) throws Exception { + private Document loadXml(String fn) throws Exception { return loadXml(new FileInputStream(fn)); } - private static Document loadXml(InputStream fn) throws Exception { + private Document loadXml(InputStream fn) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); factory.setFeature("http://xml.org/sax/features/external-general-entities", false); @@ -179,12 +212,14 @@ public class CompareUtilities extends BaseTestingUtilities { return builder.parse(fn); } - public static String checkJsonSrcIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException { - return checkJsonSrcIsSame(expected, actual, true); + public static String checkJsonSrcIsSame(String expected, String actual, JsonObject externals) throws FileNotFoundException, IOException { + return checkJsonSrcIsSame(expected, actual, true, externals); } - public static String checkJsonSrcIsSame(String expectedString, String actualString, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException { - String result = compareJsonSrc(expectedString, actualString); + public static String checkJsonSrcIsSame(String expectedString, String actualString, boolean showDiff, JsonObject externals) throws FileNotFoundException, IOException { + CompareUtilities self = new CompareUtilities(); + self.externals = externals; + String result = self.compareJsonSrc(expectedString, actualString); if (result != null && SHOW_DIFF && showDiff) { String diff = null; if (System.getProperty("os.name").contains("Linux")) @@ -217,8 +252,9 @@ public class CompareUtilities extends BaseTestingUtilities { return result; } - public static String checkJsonIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException { - String result = compareJson(expected, actual); + public static String checkJsonIsSame(String expected, String actual) throws FileNotFoundException, IOException { + CompareUtilities self = new CompareUtilities(); + String result = self.compareJson(expected, actual); if (result != null && SHOW_DIFF) { String diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe"); List command = new ArrayList(); @@ -232,22 +268,22 @@ public class CompareUtilities extends BaseTestingUtilities { return result; } - private static String compareJsonSrc(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException { - JsonObject actualJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(actual); - JsonObject expectedJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(expected); + private String compareJsonSrc(String expected, String actual) throws FileNotFoundException, IOException { + JsonObject actualJsonObject = JsonParser.parseObject(actual); + JsonObject expectedJsonObject = JsonParser.parseObject(expected); return compareObjects("", expectedJsonObject, actualJsonObject); } - private static String compareJson(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException { - JsonObject actualJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(actual)); - JsonObject expectedJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(expected)); + private String compareJson(String expected, String actual) throws FileNotFoundException, IOException { + JsonObject actualJsonObject = JsonParser.parseObject(TextFile.fileToString(actual)); + JsonObject expectedJsonObject = JsonParser.parseObject(TextFile.fileToString(expected)); return compareObjects("", expectedJsonObject, actualJsonObject); } - private static String compareObjects(String path, JsonObject expectedJsonObject, JsonObject actualJsonObject) { + private String compareObjects(String path, JsonObject expectedJsonObject, JsonObject actualJsonObject) { List optionals = listOptionals(expectedJsonObject); - for (Map.Entry en : actualJsonObject.entrySet()) { - String n = en.getKey(); + for (JsonProperty en : actualJsonObject.getProperties()) { + String n = en.getName(); if (!n.equals("fhir_comments")) { if (expectedJsonObject.has(n)) { String s = compareNodes(path + '.' + n, expectedJsonObject.get(n), en.getValue()); @@ -257,8 +293,8 @@ public class CompareUtilities extends BaseTestingUtilities { return "properties differ at " + path + ": missing property " + n; } } - for (Map.Entry en : expectedJsonObject.entrySet()) { - String n = en.getKey(); + for (JsonProperty en : expectedJsonObject.getProperties()) { + String n = en.getName(); if (!n.equals("fhir_comments") && !n.equals("$optional$") && !optionals.contains(n)) { if (!actualJsonObject.has(n)) return "properties differ at " + path + ": missing property " + n; @@ -267,38 +303,38 @@ public class CompareUtilities extends BaseTestingUtilities { return null; } - private static List listOptionals(JsonObject expectedJsonObject) { + private List listOptionals(JsonObject expectedJsonObject) { List res = new ArrayList<>(); if (expectedJsonObject.has("$optional-properties$")) { res.add("$optional-properties$"); - for (String s : JsonUtilities.strings(expectedJsonObject.getAsJsonArray("$optional-properties$"))) { + for (String s : expectedJsonObject.getStrings("$optional-properties$")) { res.add(s); } } return res; } - private static String compareNodes(String path, JsonElement expectedJsonElement, JsonElement actualJsonElement) { + private String compareNodes(String path, JsonElement expectedJsonElement, JsonElement actualJsonElement) { if (actualJsonElement.getClass() != expectedJsonElement.getClass()) return createNotEqualMessage("properties differ at " + path, expectedJsonElement.getClass().getName(), actualJsonElement.getClass().getName()); else if (actualJsonElement instanceof JsonPrimitive) { JsonPrimitive actualJsonPrimitive = (JsonPrimitive) actualJsonElement; JsonPrimitive expectedJsonPrimitive = (JsonPrimitive) expectedJsonElement; - if (actualJsonPrimitive.isBoolean() && expectedJsonPrimitive.isBoolean()) { - if (actualJsonPrimitive.getAsBoolean() != expectedJsonPrimitive.getAsBoolean()) - return createNotEqualMessage("boolean property values differ at " + path , expectedJsonPrimitive.getAsString(), actualJsonPrimitive.getAsString()); - } else if (actualJsonPrimitive.isString() && expectedJsonPrimitive.isString()) { - String actualJsonString = actualJsonPrimitive.getAsString(); - String expectedJsonString = expectedJsonPrimitive.getAsString(); + if (actualJsonPrimitive.isJsonBoolean() && expectedJsonPrimitive.isJsonBoolean()) { + if (actualJsonPrimitive.asBoolean() != expectedJsonPrimitive.asBoolean()) + return createNotEqualMessage("boolean property values differ at " + path , expectedJsonPrimitive.asString(), actualJsonPrimitive.asString()); + } else if (actualJsonPrimitive.isJsonString() && expectedJsonPrimitive.isJsonString()) { + String actualJsonString = actualJsonPrimitive.asString(); + String expectedJsonString = expectedJsonPrimitive.asString(); if (!(actualJsonString.contains(" readChoices(int offset, String s) { + private List readChoices(int offset, String s) { List list = new ArrayList<>(); s = s.substring(offset, s.length()-1); for (String p : s.split("\\|")) { @@ -384,12 +428,13 @@ public class CompareUtilities extends BaseTestingUtilities { return list; } - public static String checkTextIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException { + public static String checkTextIsSame(String expected, String actual) throws FileNotFoundException, IOException { return checkTextIsSame(expected, actual, true); } - public static String checkTextIsSame(String expectedString, String actualString, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException { - String result = compareText(expectedString, actualString); + public static String checkTextIsSame(String expectedString, String actualString, boolean showDiff) throws FileNotFoundException, IOException { + CompareUtilities self = new CompareUtilities(); + String result = self.compareText(expectedString, actualString); if (result != null && SHOW_DIFF && showDiff) { String diff = null; if (System.getProperty("os.name").contains("Linux")) @@ -423,7 +468,7 @@ public class CompareUtilities extends BaseTestingUtilities { } - private static String compareText(String expectedString, String actualString) { + private String compareText(String expectedString, String actualString) { for (int i = 0; i < Integer.min(expectedString.length(), actualString.length()); i++) { if (expectedString.charAt(i) != actualString.charAt(i)) return createNotEqualMessage("Strings differ at character " + Integer.toString(i), String.valueOf(expectedString.charAt(i)), String.valueOf(actualString.charAt(i))); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ParsingTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ParsingTests.java index 430754c14..3a14f83d1 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ParsingTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ParsingTests.java @@ -60,7 +60,7 @@ public class ParsingTests { r = new XmlParser().parse(b); b = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeBytes(r); String output = new String(b); - String msg = CompareUtilities.checkJsonSrcIsSame(src, output); + String msg = CompareUtilities.checkJsonSrcIsSame(src, output, null); Assertions.assertTrue(msg == null, msg); } diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/utils/CompareUtilitiesTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/utils/CompareUtilitiesTests.java index 5e9126bbc..a2f9af1a0 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/utils/CompareUtilitiesTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/utils/CompareUtilitiesTests.java @@ -91,7 +91,7 @@ public class CompareUtilitiesTests implements ResourceLoaderTests { final String expectedJSONPath = ROOT_JSON_TEST_PATH.resolve(expectedFileName).toString(); final String actualJSONPath = ROOT_JSON_TEST_PATH.resolve(actualFileName).toString(); - final String actualOutput = CompareUtilities.checkJsonSrcIsSame(getResourceAsString(expectedJSONPath), getResourceAsString(actualJSONPath), false); + final String actualOutput = CompareUtilities.checkJsonSrcIsSame(getResourceAsString(expectedJSONPath), getResourceAsString(actualJSONPath), false, null); if (expectedOutputFileName == null) { assertNull(actualOutput); } else { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonPrimitive.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonPrimitive.java index 7c1d76b77..90d85003e 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonPrimitive.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonPrimitive.java @@ -7,4 +7,8 @@ public abstract class JsonPrimitive extends JsonElement { public String toJson() { return getValue(); } + + public boolean asBoolean() { + return "true".equals(getValue()); + } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/TxTestsTask.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/TxTestsTask.java index 87da870d5..f6742cef8 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/TxTestsTask.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/tasks/TxTestsTask.java @@ -1,9 +1,13 @@ package org.hl7.fhir.validation.cli.tasks; +import java.io.IOException; import java.io.PrintStream; import org.hl7.fhir.utilities.SystemExitManager; import org.hl7.fhir.utilities.TimeTracker; +import org.hl7.fhir.utilities.json.JsonException; +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.utilities.json.parser.JsonParser; import org.hl7.fhir.validation.cli.model.CliContext; import org.hl7.fhir.validation.cli.utils.Params; import org.hl7.fhir.validation.special.TxTester; @@ -41,8 +45,17 @@ public class TxTestsTask extends StandaloneTask{ final String version = Params.getParam(args, Params.VERSION); final String tx = Params.getParam(args, Params.TERMINOLOGY); final String filter = Params.getParam(args, Params.FILTER); - boolean ok = new TxTester(new TxTester.InternalTxLoader(source, output), tx, false).setOutput(output).execute(version, cliContext.getModeParams(), filter); + final String externals = Params.getParam(args, Params.EXTERNALS); + boolean ok = new TxTester(new TxTester.InternalTxLoader(source, output), tx, false, loadExternals(externals)).setOutput(output).execute(version, cliContext.getModeParams(), filter); SystemExitManager.setError(ok ? 1 : 0); SystemExitManager.finish(); } + + private JsonObject loadExternals(String externals) throws JsonException, IOException { + if (externals == null) { + return null; + } else { + return JsonParser.parseObjectFromFile(externals); + } + } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index 0e1a9a36e..047f262cd 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -99,6 +99,7 @@ public class Params { public static final String SOURCE = "-source"; public static final String INPUT = "-input"; public static final String FILTER = "-filter"; + public static final String EXTERNALS = "-externals"; public static final String MODE = "-mode"; private static final String FHIR_SETTINGS_PARAM = "-fhir-settings"; private static final String WATCH_MODE_PARAM = "-watch-mode"; diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java index 70ac919b2..c597332fa 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTester.java @@ -55,17 +55,19 @@ public class TxTester { private String output; private ITerminologyClient tx; private boolean tight; + private JsonObject externals; - public TxTester(ITxTesterLoader loader, String server, boolean tight) { + public TxTester(ITxTesterLoader loader, String server, boolean tight, JsonObject externals) { super(); this.server = server; this.loader = loader; this.tight = tight; + this.externals = externals; } public static void main(String[] args) throws Exception { - new TxTester(new InternalTxLoader(args[0]), args[1], "true".equals(args[2])).execute(args[2], new ArrayList<>(), args[3]); + new TxTester(new InternalTxLoader(args[0]), args[1], "true".equals(args[2]), args.length == 5 ? JsonParser.parseObjectFromFile(args[4]) : null).execute(args[2], new ArrayList<>(), args[3]); } public boolean execute(String version, List modes, String filter) throws IOException, URISyntaxException { @@ -77,6 +79,7 @@ public class TxTester { System.out.println(" Source for tests: "+loader.describe()); System.out.println(" Output Directory: "+output); System.out.println(" Term Service Url: "+server); + System.out.println(" External Strings: "+(externals != null)); System.out.println(" Test Exec Modes: "+modes.toString()); if (version != null) { System.out.println(" Tx FHIR Version: "+version); @@ -185,12 +188,13 @@ public class TxTester { if (fo.exists()) { fo.delete(); } + JsonObject ext = externals == null ? null : externals.getJsonObject(fn); String msg = null; if (test.asString("operation").equals("expand")) { - msg = expand(tx, setup, req, resp, fp, profile); + msg = expand(tx, setup, req, resp, fp, profile, ext); } else if (test.asString("operation").equals("validate-code")) { - msg = validate(tx, setup, req, resp, fp, profile); + msg = validate(tx, setup, req, resp, fp, profile, ext); } else { throw new Exception("Unknown Operation "+test.asString("operation")); } @@ -239,7 +243,7 @@ public class TxTester { return new URI(server).getHost(); } - private String expand(ITerminologyClient tx, List setup, Parameters p, String resp, String fp, Parameters profile) throws IOException { + private String expand(ITerminologyClient tx, List setup, Parameters p, String resp, String fp, Parameters profile, JsonObject ext) throws IOException { for (Resource r : setup) { p.addParameter().setName("tx-resource").setResource(r); } @@ -255,7 +259,7 @@ public class TxTester { TxTesterScrubbers.scrubOO(oo, tight); vsj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo); } - String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj); + String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj, ext); if (diff != null) { Utilities.createDirectory(Utilities.getDirectoryForFile(fp)); TextFile.stringToFile(vsj, fp); @@ -263,7 +267,7 @@ public class TxTester { return diff; } - private String validate(ITerminologyClient tx, List setup, Parameters p, String resp, String fp, Parameters profile) throws IOException { + private String validate(ITerminologyClient tx, List setup, Parameters p, String resp, String fp, Parameters profile, JsonObject ext) throws IOException { for (Resource r : setup) { p.addParameter().setName("tx-resource").setResource(r); } @@ -279,7 +283,7 @@ public class TxTester { oo.setText(null); pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo); } - String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj); + String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj, ext); if (diff != null) { Utilities.createDirectory(Utilities.getDirectoryForFile(fp)); TextFile.stringToFile(pj, fp); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java index d409a276f..4f833e4ff 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.stream.Stream; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.NotImplementedException; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; @@ -32,6 +33,8 @@ import org.hl7.fhir.r5.comparison.StructureDefinitionComparer; import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison; import org.hl7.fhir.r5.comparison.ValueSetComparer; import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison; +import org.hl7.fhir.r5.conformance.profile.BindingResolution; +import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; import org.hl7.fhir.r5.context.BaseWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext; @@ -45,6 +48,7 @@ import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r5.renderers.CodeSystemRenderer; import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer; import org.hl7.fhir.r5.renderers.ValueSetRenderer; @@ -155,6 +159,7 @@ public class ComparisonTests { } RenderingContext lrc = new RenderingContext(context, new MarkDownProcessor(Dialect.COMMON_MARK), null, "http://hl7.org/fhir", "", "en", ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER); lrc.setDestDir(Utilities.path("[tmp]", "comparison")); + lrc.setPkp(new TestProfileKnowledgeProvider(context)); if (left instanceof CodeSystem && right instanceof CodeSystem) { CodeSystemComparer cs = new CodeSystemComparer(session); @@ -199,14 +204,15 @@ public class ComparisonTests { // String xml3 = new XhtmlComposer(true).compose(cs.renderExpansion(csc, "", "")); TextFile.stringToFile(HEADER + hd("Messages") + xmle + BREAK + hd("Metadata") + xml1 + BREAK + hd("Structure") + xml2 + FOOTER, Utilities.path("[tmp]", "comparison", name + ".html")); checkOutcomes(csc.getMessages(), content); - - lrc.setStructureMode(StructureDefinitionRendererMode.DATA_DICT); - new StructureDefinitionRenderer(lrc).render(right); - checkOutput(content.getJsonObject("version").asString("filename-dd"), right); lrc.setStructureMode(StructureDefinitionRendererMode.SUMMARY); new StructureDefinitionRenderer(lrc).render(right); checkOutput(content.getJsonObject("version").asString("filename-tree"), right); + + + lrc.setStructureMode(StructureDefinitionRendererMode.DATA_DICT); + new StructureDefinitionRenderer(lrc).render(right); + checkOutput(content.getJsonObject("version").asString("filename-dd"), right); } else if (left instanceof CapabilityStatement && right instanceof CapabilityStatement) { CapabilityStatementComparer pc = new CapabilityStatementComparer(session); CapabilityStatementComparison csc = pc.compare((CapabilityStatement) left, (CapabilityStatement) right); @@ -322,4 +328,83 @@ public class ComparisonTests { Assertions.assertEquals(output.asInteger("infoCount"), hc, "Expected " + Integer.toString(output.asInteger("infoCount")) + " hints, but found " + Integer.toString(hc) + "."); } + + public class TestProfileKnowledgeProvider implements ProfileKnowledgeProvider { + + private IWorkerContext context; + + public TestProfileKnowledgeProvider(IWorkerContext context) { + this.context = context; + } + + @Override + public boolean isDatatype(String typeSimple) { + throw new NotImplementedException(); + } + @Override + public boolean isPrimitiveType(String typeSimple) { + throw new NotImplementedException(); + } + + @Override + public boolean isResource(String typeSimple) { + throw new NotImplementedException(); + } + + @Override + public boolean hasLinkFor(String typeSimple) { + return getLinkFor(null, typeSimple) != null; + } + + @Override + public String getLinkFor(String corePath, String typeSimple) { + StructureDefinition sd = context.fetchTypeDefinition(typeSimple); + if (sd != null) { + return sd.getWebPath(); + } + return null; + } + + @Override + public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) throws FHIRException { + ValueSet vs = context.fetchResource(ValueSet.class, binding.getValueSet()); + if (vs != null) { + return new BindingResolution(vs.present(), vs.getWebPath()); + } else { + return new BindingResolution(binding.getValueSet(), null); + } + } + + @Override + public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException { + ValueSet vs = context.fetchResource(ValueSet.class, url); + if (vs != null) { + if (vs.hasWebPath()) { + return new BindingResolution(vs.present(), vs.getWebPath()); + } else { + return new BindingResolution(vs.present(), "valueset-"+vs.getIdBase()+".html"); + } + } + throw new NotImplementedException(); + } + + @Override + public String getLinkForProfile(StructureDefinition profile, String url) { + if ("http://hl7.org/fhir/StructureDefinition/Composition".equals(url)) { + return "http://hl7.org/fhir/composition.html|TestComposition"; + } + throw new NotImplementedException(); + } + + @Override + public boolean prependLinks() { + return false; + } + + @Override + public String getLinkForUrl(String corePath, String s) { + throw new NotImplementedException(); + } + + } } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/StructureMappingTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/StructureMappingTests.java index bec2dbb95..b84d0a7fc 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/StructureMappingTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/StructureMappingTests.java @@ -120,7 +120,7 @@ public class StructureMappingTests { fail(e.getMessage()); } if (output.endsWith("json")) { - msg = CompareUtilities.checkJsonSrcIsSame(s.toString(), outputJson); + msg = CompareUtilities.checkJsonSrcIsSame(s.toString(), outputJson, null); } else { TextFile.bytesToFile(s.toByteArray(), fileOutputRes); TextFile.bytesToFile(outputJson.getBytes(), fileOutputResOrig); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java index 4b14810d2..00c90d9fd 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java @@ -46,8 +46,8 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader { private JsonObject test; } - private static final String SERVER = FhirSettings.getTxFhirDevelopment(); -// private static final String SERVER = FhirSettings.getTxFhirLocal(); +// private static final String SERVER = FhirSettings.getTxFhirDevelopment(); + private static final String SERVER = FhirSettings.getTxFhirLocal(); // private static final String SERVER = "https://r4.ontoserver.csiro.au/fhir"; @@ -55,6 +55,7 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader { public static Iterable data() throws IOException { String contents = TestingUtilities.loadTestResource("tx", "test-cases.json"); + externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(TestingUtilities.loadTestResource("tx", "messages-tx.fhir.org.json")); Map examples = new HashMap(); manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents); @@ -78,6 +79,7 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader { } private static org.hl7.fhir.utilities.json.model.JsonObject manifest; + private static org.hl7.fhir.utilities.json.model.JsonObject externals; private JsonObjectPair setup; private String version = "5.0.0"; private static TxTester tester; @@ -92,7 +94,7 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader { public void test() throws Exception { if (SERVER != null) { if (tester == null) { - tester = new TxTester(this, SERVER, true); + tester = new TxTester(this, SERVER, true, externals); } String err = tester.executeTest(setup.suite, setup.test, modes); Assertions.assertTrue(err == null, err); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java index 3957efec6..f2a732704 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java @@ -76,7 +76,9 @@ public class TerminologyServiceTests { public static Iterable data() throws IOException { String contents = TestingUtilities.loadTestResource("tx", "test-cases.json"); - + String externalSource = TestingUtilities.loadTestResource("tx", "messages-tx.fhir.org.json"); + externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(externalSource); + Map examples = new HashMap(); manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents); for (org.hl7.fhir.utilities.json.model.JsonObject suite : manifest.getJsonObjects("suites")) { @@ -99,6 +101,7 @@ public class TerminologyServiceTests { } private static org.hl7.fhir.utilities.json.model.JsonObject manifest; + private static org.hl7.fhir.utilities.json.model.JsonObject externals; private JsonObjectPair setup; private String version; private String name; @@ -128,6 +131,7 @@ public class TerminologyServiceTests { String fn = setup.test.asString("response"); String resp = TestingUtilities.loadTestResource("tx", fn); String fp = Utilities.path("[tmp]", "tx", fn); + JsonObject ext = externals == null ? null : externals.getJsonObject(fn); File fo = new File(fp); if (fo.exists()) { fo.delete(); @@ -138,15 +142,15 @@ public class TerminologyServiceTests { engine.getContext().setExpansionProfile((org.hl7.fhir.r5.model.Parameters) loadResource("parameters-default.json")); } if (setup.test.asString("operation").equals("expand")) { - expand(engine, req, resp, fp); + expand(engine, req, resp, fp, ext); } else if (setup.test.asString("operation").equals("validate-code")) { - validate(engine, setup.test.asString("name"), req, resp, fp); + validate(engine, setup.test.asString("name"), req, resp, fp, ext); } else { Assertions.fail("Unknown Operation "+setup.test.asString("operation")); } } - private void expand(ValidationEngine engine, Resource req, String resp, String fp) throws IOException { + private void expand(ValidationEngine engine, Resource req, String resp, String fp, JsonObject ext) throws IOException { org.hl7.fhir.r5.model.Parameters p = ( org.hl7.fhir.r5.model.Parameters) req; ValueSet vs = engine.getContext().fetchResource(ValueSet.class, p.getParameterValue("url").primitiveValue()); boolean hierarchical = p.hasParameter("excludeNested") ? p.getParameterBool("excludeNested") == false : true; @@ -162,7 +166,7 @@ public class TerminologyServiceTests { TxTesterSorters.sortValueSet(vse.getValueset()); TxTesterScrubbers.scrubVS(vse.getValueset(), false); String vsj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(vse.getValueset()); - String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj); + String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj, ext); if (diff != null) { Utilities.createDirectory(Utilities.getDirectoryForFile(fp)); TextFile.stringToFile(vsj, fp); @@ -207,7 +211,7 @@ public class TerminologyServiceTests { TxTesterScrubbers.scrubOO(oo, false); String ooj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo); - String diff = CompareUtilities.checkJsonSrcIsSame(resp, ooj); + String diff = CompareUtilities.checkJsonSrcIsSame(resp, ooj, ext); if (diff != null) { Utilities.createDirectory(Utilities.getDirectoryForFile(fp)); TextFile.stringToFile(ooj, fp); @@ -225,7 +229,7 @@ public class TerminologyServiceTests { } } - private void validate(ValidationEngine engine, String name, Resource req, String resp, String fp) throws JsonSyntaxException, FileNotFoundException, IOException { + private void validate(ValidationEngine engine, String name, Resource req, String resp, String fp, JsonObject ext) throws JsonSyntaxException, FileNotFoundException, IOException { org.hl7.fhir.r5.model.Parameters p = (org.hl7.fhir.r5.model.Parameters) req; ValueSet vs = null; if (p.hasParameter("valueSetVersion")) { @@ -299,7 +303,7 @@ public class TerminologyServiceTests { TxTesterScrubbers.scrubParams(res); String pj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(res); - String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj); + String diff = CompareUtilities.checkJsonSrcIsSame(resp, pj, ext); if (diff != null) { Utilities.createDirectory(Utilities.getDirectoryForFile(fp)); TextFile.stringToFile(pj, fp); From 0d1072616e547cb8e57ce29d49469795797b86dd Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 17 Aug 2023 18:14:12 +1000 Subject: [PATCH 02/13] rework comparison and related rendering --- .../comparison/CanonicalResourceComparer.java | 133 +++++++-- .../CapabilityStatementComparer.java | 9 +- .../r5/comparison/CodeSystemComparer.java | 29 +- .../r5/comparison/ComparisonRenderer.java | 4 +- .../fhir/r5/comparison/ComparisonSession.java | 55 +++- .../fhir/r5/comparison/ResourceComparer.java | 6 +- .../fhir/r5/comparison/StructuralMatch.java | 1 - .../StructureDefinitionComparer.java | 208 +++++++++++--- .../fhir/r5/comparison/ValueSetComparer.java | 34 ++- .../VersionComparisonAnnotation.java | 253 ++++-------------- .../fhir/r5/renderers/CodeSystemRenderer.java | 38 +-- .../org/hl7/fhir/r5/renderers/Renderer.java | 154 +++++++++++ .../StructureDefinitionRenderer.java | 235 +++++++++++----- .../fhir/r5/renderers/ValueSetRenderer.java | 16 +- .../r5/renderers/utils/RenderingContext.java | 11 + .../fhir/r5/test/utils/CompareUtilities.java | 17 +- .../fhir/r5/utils/DefinitionNavigator.java | 23 +- .../hl7/fhir/r5/utils/PublicationHacker.java | 25 +- .../json/actualDiffArrayContent.json.error | 4 +- .../json/actualDiffArraySize.json.error | 4 +- .../json/actualDiffBoolean.json.error | 4 +- .../json/actualDiffNumber.json.error | 4 +- .../json/actualDiffType.json.error | 4 +- .../json/actualDiffValue.json.error | 4 +- .../xml/actualDiffAttribute.xml.error | 4 +- .../xml/actualDiffLocalName.xml.error | 4 +- .../xml/actualDiffNamespace.xml.error | 4 +- .../xml/actualDiffNodeType.xml.error | 4 +- .../xml/actualDiffText.xml.error | 4 +- .../comparison/tests/ComparisonTests.java | 12 +- 30 files changed, 833 insertions(+), 474 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java index 6dc96fc85..97c0a5d5d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java @@ -9,15 +9,13 @@ import java.util.Map; import java.util.Set; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.ChangeAnalysisState; -import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalType; -import org.hl7.fhir.r5.model.CapabilityStatement; import org.hl7.fhir.r5.model.CodeType; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; @@ -257,53 +255,53 @@ public abstract class CanonicalResourceComparer extends ResourceComparer { super(session); } - protected boolean compareMetadata(CanonicalResource left, CanonicalResource right, Map> comp, CanonicalResourceComparison res, List changes, Base parent, String version) { + protected boolean compareMetadata(CanonicalResource left, CanonicalResource right, Map> comp, CanonicalResourceComparison res, List changes, Base parent) { var changed = false; - if (comparePrimitivesWithTracking("url", left.getUrlElement(), right.getUrlElement(), comp, IssueSeverity.ERROR, res, parent, version)) { + if (comparePrimitivesWithTracking("url", left.getUrlElement(), right.getUrlElement(), comp, IssueSeverity.ERROR, res, parent)) { changed = true; changes.add("url"); } - if (session.getForVersion() == null) { - if (comparePrimitivesWithTracking("version", left.getVersionElement(), right.getVersionElement(), comp, IssueSeverity.ERROR, res, parent, version)) { + if (!session.isAnnotate()) { + if (comparePrimitivesWithTracking("version", left.getVersionElement(), right.getVersionElement(), comp, IssueSeverity.ERROR, res, parent)) { changed = true; changes.add("version"); } } - if (comparePrimitivesWithTracking("name", left.getNameElement(), right.getNameElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) { + if (comparePrimitivesWithTracking("name", left.getNameElement(), right.getNameElement(), comp, IssueSeverity.INFORMATION, res, parent)) { changed = true; changes.add("name"); } - if (comparePrimitivesWithTracking("title", left.getTitleElement(), right.getTitleElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) { + if (comparePrimitivesWithTracking("title", left.getTitleElement(), right.getTitleElement(), comp, IssueSeverity.INFORMATION, res, parent)) { changed = true; changes.add("title"); } - if (comparePrimitivesWithTracking("status", left.getStatusElement(), right.getStatusElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) { + if (comparePrimitivesWithTracking("status", left.getStatusElement(), right.getStatusElement(), comp, IssueSeverity.INFORMATION, res, parent)) { changed = true; changes.add("status"); } - if (comparePrimitivesWithTracking("experimental", left.getExperimentalElement(), right.getExperimentalElement(), comp, IssueSeverity.WARNING, res, parent, version)) { + if (comparePrimitivesWithTracking("experimental", left.getExperimentalElement(), right.getExperimentalElement(), comp, IssueSeverity.WARNING, res, parent)) { changed = true; changes.add("experimental"); } - if (session.getForVersion() == null) { - if (comparePrimitivesWithTracking("date", left.getDateElement(), right.getDateElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) { + if (!session.isAnnotate()) { + if (comparePrimitivesWithTracking("date", left.getDateElement(), right.getDateElement(), comp, IssueSeverity.INFORMATION, res, parent)) { changed = true; changes.add("date"); } } - if (comparePrimitivesWithTracking("publisher", left.getPublisherElement(), right.getPublisherElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) { + if (comparePrimitivesWithTracking("publisher", left.getPublisherElement(), right.getPublisherElement(), comp, IssueSeverity.INFORMATION, res, parent)) { changed = true; changes.add("publisher"); } - if (comparePrimitivesWithTracking("description", left.getDescriptionElement(), right.getDescriptionElement(), comp, IssueSeverity.NULL, res, parent, version)) { + if (comparePrimitivesWithTracking("description", left.getDescriptionElement(), right.getDescriptionElement(), comp, IssueSeverity.NULL, res, parent)) { changed = true; changes.add("description"); } - if (comparePrimitivesWithTracking("purpose", left.getPurposeElement(), right.getPurposeElement(), comp, IssueSeverity.NULL, res, parent, version)) { + if (comparePrimitivesWithTracking("purpose", left.getPurposeElement(), right.getPurposeElement(), comp, IssueSeverity.NULL, res, parent)) { changed = true; changes.add("purpose"); } - if (comparePrimitivesWithTracking("copyright", left.getCopyrightElement(), right.getCopyrightElement(), comp, IssueSeverity.INFORMATION, res, parent, version)) { + if (comparePrimitivesWithTracking("copyright", left.getCopyrightElement(), right.getCopyrightElement(), comp, IssueSeverity.INFORMATION, res, parent)) { changed = true; changes.add("copyright"); } @@ -464,29 +462,59 @@ public abstract class CanonicalResourceComparer extends ResourceComparer { } + protected boolean comparePrimitivesWithTracking(String name, List< ? extends PrimitiveType> ll, List rl, Map> comp, IssueSeverity level, CanonicalResourceComparison res, Base parent) { + boolean def = false; + + List matchR = new ArrayList<>(); + for (PrimitiveType l : ll) { + PrimitiveType r = findInList(rl, l); + if (r == null) { + session.markDeleted(parent, "element", l); + } else { + matchR.add(r); + def = comparePrimitivesWithTracking(name, l, r, comp, level, res, parent) || def; + } + } + for (PrimitiveType r : rl) { + if (!matchR.contains(r)) { + session.markAdded(r); + } + } + return def; + } + + private PrimitiveType findInList(List rl, PrimitiveType l) { + for (PrimitiveType r : rl) { + if (r.equalsDeep(l)) { + return r; + } + } + return null; + } + @SuppressWarnings("rawtypes") - protected boolean comparePrimitivesWithTracking(String name, PrimitiveType l, PrimitiveType r, Map> comp, IssueSeverity level, CanonicalResourceComparison res, Base parent, String version) { + protected boolean comparePrimitivesWithTracking(String name, PrimitiveType l, PrimitiveType r, Map> comp, IssueSeverity level, CanonicalResourceComparison res, Base parent) { StructuralMatch match = null; if (l.isEmpty() && r.isEmpty()) { match = new StructuralMatch<>(null, null, null); } else if (l.isEmpty()) { match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "Added the item '"+r.primitiveValue()+"'", fhirType()+"."+name)); - VersionComparisonAnnotation.markAdded(r, version); + session.markAdded(r); } else if (r.isEmpty()) { match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "Removed the item '"+l.primitiveValue()+"'", fhirType()+"."+name)); - VersionComparisonAnnotation.markDeleted(parent, version, name, l); + session.markDeleted(parent, name, l); } else if (!l.hasValue() && !r.hasValue()) { match = new StructuralMatch<>(null, null, vmI(IssueSeverity.INFORMATION, "No Value", fhirType()+"."+name)); } else if (!l.hasValue()) { match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "No Value on Left", fhirType()+"."+name)); - VersionComparisonAnnotation.markAdded(r, version); + session.markAdded(r); } else if (!r.hasValue()) { match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "No Value on Right", fhirType()+"."+name)); - VersionComparisonAnnotation.markDeleted(parent, version, name, l); + session.markDeleted(parent, name, l); } else if (l.getValue().equals(r.getValue())) { match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), null); } else { - VersionComparisonAnnotation.markChanged(r, version); + session.markChanged(r, l); match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), vmI(level, "Values Differ", fhirType()+"."+name)); if (level != IssueSeverity.NULL && res != null) { res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.primitiveValue()+"' vs '"+r.primitiveValue()+"'", level)); @@ -497,6 +525,65 @@ public abstract class CanonicalResourceComparer extends ResourceComparer { } return match.isDifferent(); } + + + protected boolean compareDataTypesWithTracking(String name, List< ? extends DataType> ll, List rl, Map> comp, IssueSeverity level, CanonicalResourceComparison res, Base parent) { + boolean def = false; + + List matchR = new ArrayList<>(); + for (DataType l : ll) { + DataType r = findInList(rl, l); + if (r == null) { + session.markDeleted(parent, "element", l); + } else { + matchR.add(r); + def = compareDataTypesWithTracking(name, l, r, comp, level, res, parent) || def; + } + } + for (DataType r : rl) { + if (!matchR.contains(r)) { + session.markAdded(r); + } + } + return def; + } + + private DataType findInList(List rl, DataType l) { + for (DataType r : rl) { + if (r.equalsDeep(l)) { + return r; + } + } + return null; + } + + @SuppressWarnings("rawtypes") + protected boolean compareDataTypesWithTracking(String name, DataType l, DataType r, Map> comp, IssueSeverity level, CanonicalResourceComparison res, Base parent) { + StructuralMatch match = null; + boolean le = l == null || l.isEmpty(); + boolean re = r == null || r.isEmpty(); + if (le && re) { + match = new StructuralMatch<>(null, null, null); + } else if (le) { + match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "Added the item '"+r.fhirType()+"'", fhirType()+"."+name)); + session.markAdded(r); + } else if (re) { + match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "Removed the item '"+l.fhirType()+"'", fhirType()+"."+name)); + session.markDeleted(parent, name, l); + } else if (l.equalsDeep(r)) { + match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), null); + } else { + session.markChanged(r, l); + match = new StructuralMatch<>(l.fhirType(), r.fhirType(), vmI(level, "Values Differ", fhirType()+"."+name)); + if (level != IssueSeverity.NULL && res != null) { + res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.fhirType()+"' vs '"+r.fhirType()+"'", level)); + } + } + if (comp != null) { + comp.put(name, match); + } + return match.isDifferent(); + } protected abstract String fhirType(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CapabilityStatementComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CapabilityStatementComparer.java index 2ad760f1e..5edf45867 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CapabilityStatementComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CapabilityStatementComparer.java @@ -3,17 +3,13 @@ package org.hl7.fhir.r5.comparison; import java.io.IOException; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison; -import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.model.BackboneElement; import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalType; @@ -24,13 +20,10 @@ import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResource import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestSecurityComponent; import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent; -import org.hl7.fhir.r5.model.CapabilityStatement.ResourceVersionPolicy; import org.hl7.fhir.r5.model.CodeType; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; -import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.Element; -import org.hl7.fhir.r5.model.Enumeration; import org.hl7.fhir.r5.model.Extension; import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.r5.model.Resource; @@ -114,7 +107,7 @@ public class CapabilityStatementComparer extends CanonicalResourceComparer { cs1.setStatus(left.getStatus()); cs1.setDate(new Date()); - compareMetadata(left, right, res.getMetadata(), res, new ArrayList<>(), right, session.getForVersion()); + compareMetadata(left, right, res.getMetadata(), res, new ArrayList<>(), right); comparePrimitives("kind", left.getKindElement(), right.getKindElement(), res.getMetadata(), IssueSeverity.ERROR, res); compareCanonicalList("instantiates", left.getInstantiates(), right.getInstantiates(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getInstantiates(), cs1.getInstantiates()); compareCanonicalList("imports", left.getImports(), right.getImports(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getImports(), cs1.getImports()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java index 01f3c0811..e345aad0e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java @@ -9,7 +9,6 @@ import java.util.Map; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent; @@ -132,7 +131,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer { List chMetadata = new ArrayList<>(); - boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right, session.getForVersion()); + boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right); if (comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res)) { ch = true; chMetadata.add("versionNeeded"); @@ -143,16 +142,16 @@ public class CodeSystemComparer extends CanonicalResourceComparer { } res.updatedMetadataState(ch, chMetadata); ch = false; - ch = comparePrimitivesWithTracking("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res, right, session.getForVersion()) || ch; - ch = comparePrimitivesWithTracking("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res, right, session.getForVersion()) || ch; - ch = comparePrimitivesWithTracking("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res, right, session.getForVersion()); + ch = comparePrimitivesWithTracking("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch; + ch = comparePrimitivesWithTracking("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch; + ch = comparePrimitivesWithTracking("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res, right); ch = compareProperties(left.getProperty(), right.getProperty(), res.getProperties(), res.getUnion().getProperty(), res.getIntersection().getProperty(), res.getUnion(), res.getIntersection(), res, "CodeSystem.property", right) || ch; ch = compareFilters(left.getFilter(), right.getFilter(), res.getFilters(), res.getUnion().getFilter(), res.getIntersection().getFilter(), res.getUnion(), res.getIntersection(), res, "CodeSystem.filter", right) || ch; ch = compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept", right) || ch; res.updateDefinitionsState(ch); - VersionComparisonAnnotation.annotate(right, session.getForVersion(), res); + session.annotate(right, res); return res; } @@ -193,7 +192,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer { union.add(l); res.updateContentState(true); combined.getChildren().add(new StructuralMatch(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path))); - VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "concept", l); + session.markDeleted(parent, "concept", l); } else { matchR.add(r); PropertyComponent cdM = merge(l, r, res); @@ -212,7 +211,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer { union.add(r); res.updateContentState(true); combined.getChildren().add(new StructuralMatch(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r)); - VersionComparisonAnnotation.markAdded(r, session.getForVersion()); + session.markAdded(r); } } return def; @@ -229,7 +228,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer { union.add(l); res.updateContentState(true); combined.getChildren().add(new StructuralMatch(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path))); - VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "concept", l); + session.markDeleted(parent, "concept", l); } else { matchR.add(r); CodeSystemFilterComponent cdM = merge(l, r, res); @@ -248,7 +247,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer { union.add(r); res.updateContentState(true); combined.getChildren().add(new StructuralMatch(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r)); - VersionComparisonAnnotation.markAdded(r, session.getForVersion()); + session.markAdded(r); } } return def; @@ -265,7 +264,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer { union.add(l); res.updateContentState(true); combined.getChildren().add(new StructuralMatch(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path))); - VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "concept", l); + session.markDeleted(parent, "concept", l); } else { matchR.add(r); ConceptDefinitionComponent cdM = merge(l, r, csU.getProperty(), res); @@ -287,7 +286,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer { union.add(r); res.updateContentState(true); combined.getChildren().add(new StructuralMatch(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r)); - VersionComparisonAnnotation.markAdded(r, session.getForVersion()); + session.markAdded(r); } } return def; @@ -351,19 +350,19 @@ public class CodeSystemComparer extends CanonicalResourceComparer { if (!Utilities.noString(right)) { if (Utilities.noString(left)) { msgs.add(vmI(level, "Value for "+name+" added", path)); - VersionComparisonAnnotation.markAdded(r, session.getForVersion()); + session.markAdded(r); return true; } else if (!left.equals(right)) { if (level != IssueSeverity.NULL) { res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level)); } msgs.add(vmI(level, name+" changed from left to right", path)); - VersionComparisonAnnotation.markChanged(r, session.getForVersion()); + session.markChanged(r, l); return true; } } else if (!Utilities.noString(left)) { msgs.add(vmI(level, "Value for "+name+" removed", path)); - VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "concept", l); + session.markDeleted(parent, "concept", l); return true; } return false; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java index c93ae490a..19c5cca2a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java @@ -16,11 +16,10 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.r5.comparison.CapabilityStatementComparer.CapabilityStatementComparison; import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison; -import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison; import org.hl7.fhir.r5.comparison.ResourceComparer.PlaceHolderComparison; import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison; +import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison; import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison; - import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.formats.IParser.OutputStyle; @@ -28,7 +27,6 @@ import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus; import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.Tuple; - import org.hl7.fhir.r5.model.TypeDetails; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java index f53391788..6f905935f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java @@ -8,14 +8,17 @@ import java.util.UUID; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.CanonicalResourceComparison; import org.hl7.fhir.r5.comparison.CapabilityStatementComparer.CapabilityStatementComparison; import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison; -import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison; import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison; +import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison; import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison; +import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation.AnotationType; import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CapabilityStatement; import org.hl7.fhir.r5.model.CodeSystem; @@ -32,7 +35,6 @@ public class ComparisonSession { private String sessiondId; private int count; private boolean debug; - private String forVersion; private boolean annotate; private String title; private ProfileKnowledgeProvider pkpLeft; @@ -115,7 +117,7 @@ public class ComparisonSession { return csc; } } else if (left != null) { - VersionComparisonAnnotation.markDeleted(null, forVersion, left.fhirType(), left); // todo: waht? + markDeleted(null, left.fhirType(), left); // todo: waht? String key = key(left.getUrl(), left.getVersion(), left.getUrl(), left.getVersion()); if (compares.containsKey(key)) { return compares.get(key); @@ -124,7 +126,7 @@ public class ComparisonSession { compares.put(key, csc); return csc; } else { - VersionComparisonAnnotation.markAdded(right, forVersion); + markAdded(right); String key = key(right.getUrl(), right.getVersion(), right.getUrl(), right.getVersion()); if (compares.containsKey(key)) { return compares.get(key); @@ -169,14 +171,6 @@ public class ComparisonSession { return pkpRight; } - public String getForVersion() { - return forVersion; - } - - public void setForVersion(String forVersion) { - this.forVersion = forVersion; - } - public boolean isAnnotate() { return annotate; } @@ -184,6 +178,39 @@ public class ComparisonSession { public void setAnnotate(boolean annotate) { this.annotate = annotate; } - - + + private VersionComparisonAnnotation getAnnotation(Base b) { + if (b.hasUserData(VersionComparisonAnnotation.USER_DATA_NAME)) { + return (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME); + } else { + VersionComparisonAnnotation vca = new VersionComparisonAnnotation(AnotationType.NoChange); + b.setUserData(VersionComparisonAnnotation.USER_DATA_NAME, vca); + return vca; + } + } + + public void markAdded(Base focus) { + if (isAnnotate()) { + getAnnotation(focus).added(); + } + } + + public void markChanged(Base focus, Base original) { + if (isAnnotate()) { + getAnnotation(focus).changed(original); + } + } + + public void markDeleted(Base parent, String name, Base other) { + if (isAnnotate() && other != null) { + getAnnotation(parent).deleted(name, other); + getAnnotation(other).deleted(); + } + } + + public void annotate(Base base, CanonicalResourceComparison comp) { + if (isAnnotate()) { + getAnnotation(base).comp(comp); + } + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ResourceComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ResourceComparer.java index 27d08deb7..2d37d4fab 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ResourceComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ResourceComparer.java @@ -3,8 +3,6 @@ package org.hl7.fhir.r5.comparison; import java.util.ArrayList; import java.util.List; -import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; -import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.StructureDefinition; @@ -326,5 +324,7 @@ public class ResourceComparer { } return cell; } - + + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructuralMatch.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructuralMatch.java index 6ea63ebe3..dae998ace 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructuralMatch.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructuralMatch.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.List; import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; -import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructureDefinitionComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructureDefinitionComparer.java index 9c50790f4..cf816826c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructureDefinitionComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructureDefinitionComparer.java @@ -5,12 +5,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; -import java.util.Map; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; -import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.CanonicalResourceComparison; import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison; import org.hl7.fhir.r5.conformance.profile.BindingResolution; import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; @@ -19,7 +17,6 @@ import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.formats.IParser; import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.model.Base; -import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.ElementDefinition; @@ -43,10 +40,8 @@ import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; import org.hl7.fhir.r5.utils.DefinitionNavigator; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; -import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; -import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; @@ -147,7 +142,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple sd1.setDate(new Date()); List chMetadata = new ArrayList<>(); - boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right, session.getForVersion()); + boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right); if (comparePrimitives("fhirVersion", left.getFhirVersionElement(), right.getFhirVersionElement(), res.getMetadata(), IssueSeverity.WARNING, res)) { ch = true; chMetadata.add("fhirVersion"); @@ -178,7 +173,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple } res.updateDefinitionsState(ch); - VersionComparisonAnnotation.annotate(right, session.getForVersion(), res); + session.annotate(right, res); return res; } @@ -228,19 +223,19 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple // descriptive properties from ElementDefinition - merge them: subset.setLabel(mergeText(comp, res, path, "label", left.current().getLabel(), right.current().getLabel(), false)); - comparePrimitivesWithTracking("label", left.current().getLabelElement(), right.current().getLabelElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion()); + comparePrimitivesWithTracking("label", left.current().getLabelElement(), right.current().getLabelElement(), null, IssueSeverity.INFORMATION, comp, right.current()); subset.setShort(mergeText(comp, res, path, "short", left.current().getShort(), right.current().getShort(), false)); - def = comparePrimitivesWithTracking("short", left.current().getShortElement(), right.current().getShortElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion()) || def; + def = comparePrimitivesWithTracking("short", left.current().getShortElement(), right.current().getShortElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def; subset.setDefinition(mergeText(comp, res, path, "definition", left.current().getDefinition(), right.current().getDefinition(), false)); - def = comparePrimitivesWithTracking("definition", left.current().getDefinitionElement(), right.current().getDefinitionElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion()) || def; + def = comparePrimitivesWithTracking("definition", left.current().getDefinitionElement(), right.current().getDefinitionElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def; subset.setComment(mergeText(comp, res, path, "comments", left.current().getComment(), right.current().getComment(), false)); - def = comparePrimitivesWithTracking("comment", left.current().getCommentElement(), right.current().getCommentElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion()) || def; + def = comparePrimitivesWithTracking("comment", left.current().getCommentElement(), right.current().getCommentElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def; subset.setRequirements(mergeText(comp, res, path, "requirements", left.current().getRequirements(), right.current().getRequirements(), false)); - def = comparePrimitivesWithTracking("requirements", left.current().getRequirementsElement(), right.current().getRequirementsElement(), null, IssueSeverity.INFORMATION, comp, right.current(), session.getForVersion()) || def; + def = comparePrimitivesWithTracking("requirements", left.current().getRequirementsElement(), right.current().getRequirementsElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def; subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode())); subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias())); @@ -253,12 +248,12 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple } subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport()); - def = comparePrimitivesWithTracking("mustSupport", left.current().getMustSupportElement(), right.current().getMustSupportElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; + def = comparePrimitivesWithTracking("mustSupport", left.current().getMustSupportElement(), right.current().getMustSupportElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def; ElementDefinition superset = subset.copy(); - def = comparePrimitivesWithTracking("min", left.current().getMinElement(), right.current().getMinElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; - def = comparePrimitivesWithTracking("max", left.current().getMaxElement(), right.current().getMaxElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; + def = comparePrimitivesWithTracking("min", left.current().getMinElement(), right.current().getMinElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def; + def = comparePrimitivesWithTracking("max", left.current().getMaxElement(), right.current().getMaxElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def; // compare and intersect int leftMin = left.current().getMin(); @@ -396,6 +391,8 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple assert(left.path().equals(right.path())); boolean def = false; + boolean ch = false; + System.out.println(left.getId()); // not allowed to be different: // ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path); @@ -403,25 +400,71 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple // ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - this check belongs in the core // ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); - so does this - if (left.current() == null && right.current() == null) { + ElementDefinition edl = left.current(); + ElementDefinition edr = right.current(); + if (edl == null && edr == null) { // both are sparse at this point, do nothing - } else if (left.current() == null) { - VersionComparisonAnnotation.markAdded(right.current(), session.getForVersion()); - } else if (right.current() == null) { - VersionComparisonAnnotation.markDeleted(right.parent(), session.getForVersion(), "element", left.current()); + } else if (edl == null) { + session.markAdded(edr); + } else if (edr == null) { + session.markDeleted(right.parent(), "element", edl); } else { - // descriptive properties from ElementDefinition - merge them: - comparePrimitivesWithTracking("label", left.current().getLabelElement(), right.current().getLabelElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()); - def = comparePrimitivesWithTracking("short", left.current().getShortElement(), right.current().getShortElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; - def = comparePrimitivesWithTracking("definition", left.current().getDefinitionElement(), right.current().getDefinitionElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; - def = comparePrimitivesWithTracking("comment", left.current().getCommentElement(), right.current().getCommentElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; - def = comparePrimitivesWithTracking("requirements", left.current().getRequirementsElement(), right.current().getRequirementsElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; - def = comparePrimitivesWithTracking("mustSupport", left.current().getMustSupportElement(), right.current().getMustSupportElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; - def = comparePrimitivesWithTracking("min", left.current().getMinElement(), right.current().getMinElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; - def = comparePrimitivesWithTracking("max", left.current().getMaxElement(), right.current().getMaxElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def; + // descriptive properties from ElementDefinition + comparePrimitivesWithTracking("label", edl.getLabelElement(), edr.getLabelElement(), null, IssueSeverity.INFORMATION, null, edr); + comparePrimitivesWithTracking("sliceName", edl.getSliceNameElement(), edr.getSliceNameElement(), null, IssueSeverity.INFORMATION, null, edr); + comparePrimitivesWithTracking("sliceIsConstraining", edl.getSliceIsConstrainingElement(), edr.getSliceIsConstrainingElement(), null, IssueSeverity.INFORMATION, null, edr); + comparePrimitivesWithTracking("alias", edl.getAlias(), edr.getAlias(), null, IssueSeverity.INFORMATION, null, edr); + compareDataTypesWithTracking("code", edl.getCode(), edr.getCode(), null, IssueSeverity.INFORMATION, null, edr); + + def = comparePrimitivesWithTracking("short", edl.getShortElement(), edr.getShortElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + def = comparePrimitivesWithTracking("definition", edl.getDefinitionElement(), edr.getDefinitionElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + def = comparePrimitivesWithTracking("comment", edl.getCommentElement(), edr.getCommentElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + def = comparePrimitivesWithTracking("requirements", edl.getRequirementsElement(), edr.getRequirementsElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + def = comparePrimitivesWithTracking("mustSupport", edl.getMustSupportElement(), edr.getMustSupportElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + def = comparePrimitivesWithTracking("meaningWhenMissing", edl.getMeaningWhenMissingElement(), edr.getMeaningWhenMissingElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + def = comparePrimitivesWithTracking("isModifierReason", edl.getIsModifierReasonElement(), edr.getIsModifierReasonElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + + ch = comparePrimitivesWithTracking("min", edl.getMinElement(), edr.getMinElement(), null, IssueSeverity.ERROR, null, edr) || ch; + ch = comparePrimitivesWithTracking("max", edl.getMaxElement(), edr.getMaxElement(), null, IssueSeverity.ERROR, null, edr) || ch; + ch = compareDataTypesWithTracking("defaultValue", edl.getDefaultValue(), edr.getDefaultValue(), null, IssueSeverity.ERROR, null, edr) || ch; + ch = compareDataTypesWithTracking("fixed", edl.getFixed(), edr.getFixed(), null, IssueSeverity.ERROR, null, edr) || ch; + ch = compareDataTypesWithTracking("pattern", edl.getPattern(), edr.getPattern(), null, IssueSeverity.ERROR, null, edr) || ch; + ch = compareDataTypesWithTracking("minValue", edl.getMinValue(), edr.getMinValue(), null, IssueSeverity.ERROR, null, edr) || ch; + ch = compareDataTypesWithTracking("maxValue", edl.getMaxValue(), edr.getMaxValue(), null, IssueSeverity.ERROR, null, edr) || ch; + ch = comparePrimitivesWithTracking("maxLength", edl.getMaxLengthElement(), edr.getMaxLengthElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + ch = comparePrimitivesWithTracking("mustHaveValue", edl.getMustHaveValueElement(), edr.getMustHaveValueElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + ch = comparePrimitivesWithTracking("valueAlternatives", edl.getValueAlternatives(), edr.getValueAlternatives(), null, IssueSeverity.INFORMATION, null, edr) || ch; + ch = comparePrimitivesWithTracking("isModifier", edl.getIsModifierElement(), edr.getIsModifierElement(), null, IssueSeverity.INFORMATION, null, edr) || def; + + def = compareTypes(path, sliceName, edl, edr, res) || def; + + + ElementDefinitionBindingComponent bl = edl.getBinding(); + ElementDefinitionBindingComponent br = edr.getBinding(); + if (bl == null && br == null) { + // both are sparse at this point, do nothing + } else if (bl == null) { + session.markAdded(edr); + } else if (br == null) { + session.markDeleted(right.parent(), "element", edl); + } else { + ch = comparePrimitivesWithTracking("strength", bl.getStrengthElement(), br.getStrengthElement(), null, IssueSeverity.ERROR, null, edr) || ch; + def = comparePrimitivesWithTracking("description", bl.getDescriptionElement(), br.getDescriptionElement(), null, IssueSeverity.ERROR, null, edr) || def; + ch = comparePrimitivesWithTracking("valueSet", bl.getValueSetElement(), br.getValueSetElement(), null, IssueSeverity.ERROR, null, edr) || ch; + // todo: additional + } + + def = compareInvariants(path, sliceName, edl, edr, res) || def; + + // main todos: + // invariants, slicing + // mappings } // add the children - def = compareDiffChildren(path, left, right, right.current() == null ? parent : right.current(), res) || def; + if (ch) { + res.updateContentState(true); + } + def = compareDiffChildren(path, left, right, edr == null ? parent : edr, res) || def; // // // now process the slices // if (left.current().hasSlicing() || right.current().hasSlicing()) { @@ -484,7 +527,6 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple return def; } - private boolean compareDiffChildren(String path, DefinitionNavigator left, DefinitionNavigator right, Base parent, ProfileComparison res) throws DefinitionException, IOException, FHIRFormatError { boolean def = false; @@ -495,7 +537,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple for (DefinitionNavigator l : lc) { DefinitionNavigator r = findInList(rc, l); if (r == null) { - VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "element", l.current()); + session.markDeleted(parent, "element", l.current()); res.updateContentState(true); } else { matchR.add(r); @@ -504,7 +546,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple } for (DefinitionNavigator r : rc) { if (!matchR.contains(r)) { - VersionComparisonAnnotation.markAdded(r.current(), session.getForVersion()); + session.markAdded(r.current()); res.updateContentState(true); } } @@ -512,14 +554,112 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple } private DefinitionNavigator findInList(List rc, DefinitionNavigator l) { + String s = l.getId(); for (DefinitionNavigator t : rc) { - if (tail(t.current().getPath()).equals(tail(l.current().getPath()))) { + String ts = t.getId(); + if (tail(ts).equals(tail(s))) { return t; } } return null; } + private boolean compareTypes(String path, String sliceName, ElementDefinition left, ElementDefinition right, ProfileComparison res) { + boolean def = false; + + List matchR = new ArrayList<>(); + for (TypeRefComponent l : left.getType()) { + TypeRefComponent r = findInList(right.getType(), l); + if (r == null) { + session.markDeleted(right, "type", l); + res.updateContentState(true); + } else { + matchR.add(r); + def = compareType(path+".type", l, r, res) || def; + } + } + for (TypeRefComponent r : right.getType()) { + if (!matchR.contains(r)) { + session.markAdded(r); + res.updateContentState(true); + } + } + return def; + } + + private TypeRefComponent findInList(List rc, TypeRefComponent l) { + for (TypeRefComponent t : rc) { + if (t.getCodeElement().equalsDeep(l.getCodeElement())) { + return t; + } + } + return null; + } + + + private boolean compareType(String string, TypeRefComponent l, TypeRefComponent r, ProfileComparison res) { + boolean def = false; + boolean ch = false; + // codes must match + ch = comparePrimitivesWithTracking("profile", l.getProfile(), r.getProfile(), null, IssueSeverity.ERROR, null, r) || ch; + ch = comparePrimitivesWithTracking("targetProfile", l.getTargetProfile(), r.getTargetProfile(), null, IssueSeverity.ERROR, null, r) || ch; + ch = comparePrimitivesWithTracking("aggregation", l.getAggregation(), r.getAggregation(), null, IssueSeverity.ERROR, null, r) || ch; + def = comparePrimitivesWithTracking("versioning", l.getVersioningElement(), r.getVersioningElement(), null, IssueSeverity.INFORMATION, null, r) || def; + if (ch) { + res.updateContentState(true); + } + return def; + } + + + private boolean compareInvariants(String path, String sliceName, ElementDefinition left, ElementDefinition right, ProfileComparison res) { + boolean def = false; + + List matchR = new ArrayList<>(); + for (ElementDefinitionConstraintComponent l : left.getConstraint()) { + ElementDefinitionConstraintComponent r = findInList(right.getConstraint(), l); + if (r == null) { + session.markDeleted(right, "invariant", l); + res.updateContentState(true); + } else { + matchR.add(r); + def = compareInvariant(path+".type", l, r, res) || def; + } + } + for (ElementDefinitionConstraintComponent r : right.getConstraint()) { + if (!matchR.contains(r)) { + session.markAdded(r); + res.updateContentState(true); + } + } + return def; + } + + private ElementDefinitionConstraintComponent findInList(List rc, ElementDefinitionConstraintComponent l) { + for (ElementDefinitionConstraintComponent t : rc) { + if (t.getKeyElement().equalsDeep(l.getKeyElement())) { + return t; + } + } + return null; + } + + + private boolean compareInvariant(String string, ElementDefinitionConstraintComponent l, ElementDefinitionConstraintComponent r, ProfileComparison res) { + boolean def = false; + boolean ch = false; + // codes must match + def = comparePrimitivesWithTracking("requirements", l.getRequirementsElement(), r.getRequirementsElement(), null, IssueSeverity.INFORMATION, null, r) || def; + ch = comparePrimitivesWithTracking("severity", l.getSeverityElement(), r.getSeverityElement(), null, IssueSeverity.ERROR, null, r) || ch; + comparePrimitivesWithTracking("suppress", l.getSuppressElement(), r.getSuppressElement(), null, IssueSeverity.INFORMATION, null, r); + def = comparePrimitivesWithTracking("human", l.getHumanElement(), r.getHumanElement(), null, IssueSeverity.INFORMATION, null, r) || def; + ch = comparePrimitivesWithTracking("expression", l.getExpressionElement(), r.getExpressionElement(), null, IssueSeverity.ERROR, null, r) || ch; + if (ch) { + res.updateContentState(true); + } + return def; + } + // private void ruleEqual(ProfileComparison comp, StructuralMatch res, DataType vLeft, DataType vRight, String name, String path) throws IOException { // if (vLeft == null && vRight == null) { // // nothing diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java index a4e7229c1..5cba9a5c8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java @@ -7,8 +7,6 @@ import java.util.List; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; -import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation.AnotationType; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.Element; @@ -125,7 +123,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { vs1.setDate(new Date()); List chMetadata = new ArrayList<>(); - var ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right, session.getForVersion()); + var ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right); var def = false; if (comparePrimitives("immutable", left.getImmutableElement(), right.getImmutableElement(), res.getMetadata(), IssueSeverity.WARNING, res)) { ch = true; @@ -143,7 +141,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { def = compareCompose(left.getCompose(), right.getCompose(), res, res.getUnion().getCompose(), res.getIntersection().getCompose()) || def; res.updateDefinitionsState(def); // compareExpansions(left, right, res); - VersionComparisonAnnotation.annotate(right, session.getForVersion(), res); + session.annotate(right, res); return res; } @@ -157,7 +155,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { union.getInclude().add(l); res.updateContentState(true); res.getIncludes().getChildren().add(new StructuralMatch(l, vmI(IssueSeverity.INFORMATION, "Removed Include", "ValueSet.compose.include"))); - VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "include", l); + session.markDeleted(right, "include", l); } else { matchR.add(r); ConceptSetComponent csM = new ConceptSetComponent(); @@ -174,7 +172,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { union.getInclude().add(r); res.updateContentState(true); res.getIncludes().getChildren().add(new StructuralMatch(vmI(IssueSeverity.INFORMATION, "Added Include", "ValueSet.compose.include"), r)); - VersionComparisonAnnotation.markAdded(r, session.getForVersion()); + session.markAdded(r); } } @@ -252,7 +250,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { res.updateContentState(true); combined.getChildren().add(new StructuralMatch(l, vmI(IssueSeverity.ERROR, "Removed ValueSet", "ValueSet.compose.include.valueSet"))); if (session.isAnnotate()) { - VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "valueset", l); + session.markDeleted(right, "valueset", l); } } else { matchVSR.add(r); @@ -269,7 +267,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { StructuralMatch sm = new StructuralMatch(l, r, vmI(IssueSeverity.WARNING, "Values are different", "ValueSet.compose.include.valueSet")); combined.getChildren().add(sm); if (session.isAnnotate()) { - VersionComparisonAnnotation.markChanged(r, session.getForVersion()); + session.markChanged(r, l); } } @@ -280,7 +278,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { union.getValueSet().add(r); res.updateContentState(true); combined.getChildren().add(new StructuralMatch(vmI(IssueSeverity.ERROR, "Add ValueSet", "ValueSet.compose.include.valueSet"), r)); - VersionComparisonAnnotation.markAdded(r, session.getForVersion()); + session.markAdded(r); } } @@ -292,7 +290,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { res.updateContentState(true); combined.getChildren().add(new StructuralMatch(l, vmI(IssueSeverity.ERROR, "Removed this Concept", "ValueSet.compose.include.concept"))); res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" removed", IssueSeverity.ERROR)); - VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "concept", l); + session.markDeleted(right,"concept", l); } else { matchCR.add(r); if (l.getCode().equals(r.getCode())) { @@ -311,7 +309,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { combined.getChildren().add(sm); res.updateContentState(true); compareConcepts(path+".concept["+right.getConcept().indexOf(r)+"]", l, r, sm, null, null, res); - VersionComparisonAnnotation.markChanged(r, session.getForVersion()); + session.markChanged(r, l); } } } @@ -321,7 +319,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { res.updateContentState(true); combined.getChildren().add(new StructuralMatch(vmI(IssueSeverity.ERROR, "Added this Concept", "ValueSet.compose.include.concept"), r)); res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+r.getCode()+" added", IssueSeverity.ERROR)); - VersionComparisonAnnotation.markAdded(r, session.getForVersion()); + session.markAdded(r); } } @@ -332,7 +330,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { union.getFilter().add(l); res.updateContentState(true); combined.getChildren().add(new StructuralMatch(l, vmI(IssueSeverity.ERROR, "Removed this item", "ValueSet.compose.include.filter"))); - VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "filter", l); + session.markDeleted(right, "filter", l); } else { matchFR.add(r); if (l.getProperty().equals(r.getProperty()) && l.getOp().equals(r.getOp())) { @@ -344,7 +342,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { combined.getChildren().add(sm); if (!compareFilters(l, r, sm, cu, ci)) { res.updateContentState(true); - VersionComparisonAnnotation.markChanged(r, session.getForVersion()); + session.markChanged(r, l); } } else { union.getFilter().add(l); @@ -361,7 +359,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { union.getFilter().add(r); res.updateContentState(true); combined.getChildren().add(new StructuralMatch(vmI(IssueSeverity.ERROR, "Added this item", "ValueSet.compose.include.filter"), r)); - VersionComparisonAnnotation.markAdded(r, session.getForVersion()); + session.markAdded(r); } } return def; @@ -383,10 +381,10 @@ public class ValueSetComparer extends CanonicalResourceComparer { def = !l.getDisplay().equals(r.getDisplay()); if (def) { res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" display changed from '"+l.getDisplay()+"' to '"+r.getDisplay()+"'", IssueSeverity.WARNING)); - VersionComparisonAnnotation.markChanged(r.getDisplayElement(), session.getForVersion()); + session.markChanged(r.getDisplayElement(), l.getDisplayElement()); } } else if (l.hasDisplay()) { - VersionComparisonAnnotation.markDeleted(r, session.getForVersion(), "display", l.getDisplayElement()); + session.markDeleted(r, "display", l.getDisplayElement()); res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" display '"+l.getDisplay()+"' removed", IssueSeverity.WARNING)); sm.getChildren().add(new StructuralMatch(l.getDisplayElement(), null, vmI(IssueSeverity.INFORMATION, "Display Removed", "ValueSet.compose.include.concept"))); if (ci != null) { @@ -395,7 +393,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { } def = true; } else if (r.hasDisplay()) { - VersionComparisonAnnotation.markAdded(r.getDisplayElement(), session.getForVersion()); + session.markAdded(r.getDisplayElement()); res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" display '"+r.getDisplay()+"' added", IssueSeverity.WARNING)); sm.getChildren().add(new StructuralMatch(null, r.getDisplayElement(), vmI(IssueSeverity.INFORMATION, "Display added", "ValueSet.compose.include.concept"))); if (ci != null) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java index 318d48040..5ed742f33 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java @@ -6,203 +6,87 @@ import java.util.List; import java.util.Map; import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.CanonicalResourceComparison; -import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.ChangeAnalysisState; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalResource; -import org.hl7.fhir.r5.model.CodeSystem; -import org.hl7.fhir.r5.model.StringType; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; -import org.hl7.fhir.utilities.xhtml.NodeType; -import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class VersionComparisonAnnotation { public enum AnotationType { - NoChange, Added, Changed, ChildrenDeleted, Deleted; + NoChange, Added, Changed, Deleted; } public static final String USER_DATA_NAME = "version-annotation"; - + private AnotationType type; - + private String original; private Map> deletedChildren; - - private String version; - private CanonicalResourceComparison comp; - - private VersionComparisonAnnotation(AnotationType type, String version) { + + public VersionComparisonAnnotation(AnotationType type) { super(); this.type = type; - this.version = version; } - public static void annotate(Base base, String version, CanonicalResourceComparison comp) { - if (version != null) { - VersionComparisonAnnotation vca = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME); - if (vca == null) { - vca = new VersionComparisonAnnotation(comp.noUpdates() ? AnotationType.NoChange : AnotationType.Changed, version); - base.setUserData(USER_DATA_NAME, vca); - } - vca.comp = comp; - } + public void added() { + assert type == AnotationType.NoChange; + type = AnotationType.Added; } - - public static void markAdded(Base focus, String version) { - if (version != null) { - focus.setUserData(USER_DATA_NAME, new VersionComparisonAnnotation(AnotationType.Added, version)); + public void changed(Base orig) { + assert type == AnotationType.NoChange; + type = AnotationType.Changed; + if (original != null && orig.isPrimitive() && orig.primitiveValue().length() < 120) { // arbitrary, but we don't a whack of markdown + this.original = orig.primitiveValue(); } } - public static void markChanged(Base focus, String version) { - if (version != null) { - focus.setUserData(USER_DATA_NAME, new VersionComparisonAnnotation(AnotationType.Changed, version)); - } + public void deleted() { + assert type == AnotationType.NoChange; + type = AnotationType.Deleted; + + } - public static void markDeleted(Base parent, String version, String name, Base other) { - if (version != null && other != null) { - VersionComparisonAnnotation vca = null; - if (parent.hasUserData(USER_DATA_NAME)) { - vca = (VersionComparisonAnnotation) parent.getUserData(USER_DATA_NAME); - assert vca.type != AnotationType.Added; - } else { - vca = new VersionComparisonAnnotation(AnotationType.ChildrenDeleted, version); - parent.setUserData(USER_DATA_NAME, vca); - } - if (vca.deletedChildren == null) { - vca.deletedChildren = new HashMap<>(); - } - if (!vca.deletedChildren.containsKey(name)) { - vca.deletedChildren.put(name, new ArrayList<>()); - } - other.setUserData(USER_DATA_NAME, new VersionComparisonAnnotation(AnotationType.Deleted, version)); - vca.deletedChildren.get(name).add(other); + public void deleted(String name, Base other) { + if (deletedChildren == null) { + deletedChildren = new HashMap<>(); } + if (!deletedChildren.containsKey(name)) { + deletedChildren.put(name, new ArrayList<>()); + } + deletedChildren.get(name).add(other); + } + + public void comp(CanonicalResourceComparison comp) { + assert this.comp == null; + // TODO Auto-generated method stub + if (!comp.noUpdates()) { + type = AnotationType.Changed; + } + this.comp = comp; + } + + public static String getUserDataName() { + return USER_DATA_NAME; } public AnotationType getType() { return type; } - public void setType(AnotationType type) { - this.type = type; - } - - public static XhtmlNode render(Base b, XhtmlNode x) { - if (b.hasUserData(USER_DATA_NAME)) { - VersionComparisonAnnotation self = (VersionComparisonAnnotation) b.getUserData(USER_DATA_NAME); - return self.render(x); - } else { - return x; - } - } - - private XhtmlNode render(XhtmlNode x) { - switch (type) { - case Added: - XhtmlNode spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); - XhtmlNode spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); - spanInner.img("icon-change-add.png", "icon"); - spanInner.tx(" Added:"); - return spanOuter; - case Changed: - spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); - spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version); - spanInner.img("icon-change-edit.png", "icon"); - spanInner.tx(" Changed:"); - return spanOuter; - case Deleted: - spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); - spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+version); - spanInner.img("icon-change-remove.png", "icon"); - spanInner.tx(" Removed:"); - return spanOuter.strikethrough(); - default: - return x; - } + + public String getOriginal() { + return original; } - public static XhtmlNode renderDiv(Base b, XhtmlNode x) { - if (b.hasUserData(USER_DATA_NAME)) { - VersionComparisonAnnotation self = (VersionComparisonAnnotation) b.getUserData(USER_DATA_NAME); - return self.renderDiv(x); - } else { - return x; - } + public Map> getDeletedChildren() { + return deletedChildren; } - - private XhtmlNode renderDiv(XhtmlNode x) { - switch (type) { - case Added: - XhtmlNode divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); - XhtmlNode spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); - spanInner.img("icon-change-add.png", "icon"); - spanInner.tx(" Added:"); - return divOuter; - case Changed: - divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); - spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version); - spanInner.img("icon-change-edit.png", "icon"); - spanInner.tx(" Changed:"); - return divOuter; - case Deleted: - divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); - spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+version); - spanInner.img("icon-change-remove.png", "icon"); - spanInner.tx(" Removed:"); - return divOuter.strikethrough(); - default: - return x; - } - } - - public static XhtmlNode renderRow(Base b, XhtmlNode tbl, XhtmlNode tr) { - if (b.hasUserData(USER_DATA_NAME)) { - VersionComparisonAnnotation self = (VersionComparisonAnnotation) b.getUserData(USER_DATA_NAME); - return self.renderRow(tbl, tr); - } else { - return tr.td(); - } - } - - private XhtmlNode renderRow(XhtmlNode tbl, XhtmlNode tr) { - switch (type) { - case Added: - if (tbl.isClass("grid")) { - tr.style("border: solid 1px #dddddd; margin: 2px; padding: 2px"); - } - XhtmlNode td = tr.td(); - XhtmlNode span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+version); - span.img("icon-change-add.png", "icon"); - span.tx(" Added:"); - XhtmlNode x = new XhtmlNode(NodeType.Element, "holder"); - x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+version).tx(" "); - tr.styleCells(x); - return td; - case Changed: - td = tr.td(); - span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been changed since "+version); - span.img("icon-change-edit.png", "icon"); - span.tx(" Changed:"); - return td; - case Deleted: - tr.style("text-decoration: line-through"); - td = tr.td(); - span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+version); - span.img("icon-change-remove.png", "icon"); - span.tx(" Removed:"); - x = new XhtmlNode(NodeType.Element, "holder"); - x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px; text-decoration: none", "This row of content has been added since "+version).tx(" "); - tr.styleCells(x); - return td; - default: - return tr.td(); - } + public CanonicalResourceComparison getComp() { + return comp; } + public static boolean hasDeleted(Base base, String... names) { boolean result = false; if (base.hasUserData(USER_DATA_NAME)) { @@ -239,50 +123,5 @@ public class VersionComparisonAnnotation { } return result.isEmpty() ? null : result.get(0); } - - - public static CanonicalResourceComparison artifactComparison(Base base) { - if (base.hasUserData(USER_DATA_NAME)) { - VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME); - return self.comp; - } else { - return null; - } - } - public static void renderSummary(Base base, XhtmlNode x, String version, String... metadataFields) { - if (base.hasUserData(USER_DATA_NAME)) { - VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME); - switch (self.type) { - case Added: - XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); - spanInner.img("icon-change-add.png", "icon"); - spanInner.tx(" Added"); - return; - case Changed: - if (self.comp.noChangeOtherThanMetadata(metadataFields)) { - x.span("color: #eeeeee").tx("n/c"); - return; - } else { - spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); - spanInner.img("icon-change-edit.png", "icon"); - spanInner.tx(" Changed"); - } - return; - case Deleted: - spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); - spanInner.img("icon-change-remove.png", "icon"); - spanInner.tx(" Removed"); - return; - default: - x.span("color: #eeeeee").tx("n/c"); - return; - } - } else { - x.span("color: #eeeeee").tx("--"); - } - } - - - } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java index 16c232e39..727707172 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java @@ -88,12 +88,12 @@ public class CodeSystemRenderer extends TerminologyRenderer { tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Value", getContext().getLang())); for (CodeSystemFilterComponent f : cs.getFilter()) { tr = tbl.tr(); - VersionComparisonAnnotation.render(f, tr.td()).tx(f.getCode()); - VersionComparisonAnnotation.render(f.getDescriptionElement(), tr.td()).tx(f.getDescription()); + renderStatus(f, tr.td()).tx(f.getCode()); + renderStatus(f.getDescriptionElement(), tr.td()).tx(f.getDescription()); XhtmlNode td = tr.td(); for (Enumeration t : f.getOperator()) - VersionComparisonAnnotation.render(t, td).tx(t.asStringValue()+" "); - VersionComparisonAnnotation.render(f.getValueElement(), tr.td()).tx(f.getValue()); + renderStatus(t, td).tx(t.asStringValue()+" "); + renderStatus(f.getValueElement(), tr.td()).tx(f.getValue()); } } } @@ -129,13 +129,13 @@ public class CodeSystemRenderer extends TerminologyRenderer { if (hasRendered) { tr.td().tx(ToolingExtensions.getPresentation(p, p.getCodeElement())); } - VersionComparisonAnnotation.render(p, tr.td()).tx(p.getCode()); + renderStatus(p, tr.td()).tx(p.getCode()); if (hasURI) { - VersionComparisonAnnotation.render(p.getUriElement(), tr.td()).tx(p.getUri()); + renderStatus(p.getUriElement(), tr.td()).tx(p.getUri()); } - VersionComparisonAnnotation.render(p.getTypeElement(), tr.td()).tx(p.hasType() ? p.getType().toCode() : ""); + renderStatus(p.getTypeElement(), tr.td()).tx(p.hasType() ? p.getType().toCode() : ""); if (hasDescription) { - VersionComparisonAnnotation.render(p.getDescriptionElement(), tr.td()).tx(p.getDescription()); + renderStatus(p.getDescriptionElement(), tr.td()).tx(p.getDescription()); } } return true; @@ -174,7 +174,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { x.para().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Concepts", getContext().getLang())); } XhtmlNode p = x.para(); - VersionComparisonAnnotation.render(cs.getUrlElement(), p.param("cs")).code().tx(cs.getUrl()); + renderStatus(cs.getUrlElement(), p.param("cs")).code().tx(cs.getUrl()); makeCasedParam(p.param("cased"), cs, cs.getCaseSensitiveElement()); makeHierarchyParam(p.param("h"), cs, cs.getHierarchyMeaningElement()); @@ -247,7 +247,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { private void makeHierarchyParam(XhtmlNode x, CodeSystem cs, Enumeration hm) { if (hm.hasValue()) { String s = hm.getValue().getDisplay(); - VersionComparisonAnnotation.render(hm, x).tx(" in a "+s+" heirarchy"); + renderStatus(hm, x).tx(" in a "+s+" heirarchy"); } else if (VersionComparisonAnnotation.hasDeleted(cs, "hierarchyMeaning")) { makeHierarchyParam(x, null, (Enumeration) VersionComparisonAnnotation.getDeleted(cs, "hierarchyMeaning").get(0)); } else if (CodeSystemUtilities.hasHierarchy(cs)) { @@ -260,7 +260,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { private void makeCasedParam(XhtmlNode x, CodeSystem cs, BooleanType caseSensitiveElement) { if (caseSensitiveElement.hasValue()) { String s = caseSensitiveElement.getValue() == true? "case-sensitive" : "case-insensitive"; - VersionComparisonAnnotation.render(caseSensitiveElement, x).tx(s); + renderStatus(caseSensitiveElement, x).tx(s); } else if (VersionComparisonAnnotation.hasDeleted(cs, "caseSensitive")) { makeCasedParam(x, null, (BooleanType) VersionComparisonAnnotation.getDeleted(cs, "caseSensitive").get(0)); } else { @@ -398,7 +398,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { tr.setAttribute("style", "background-color: #ffeeee"); } - XhtmlNode td = VersionComparisonAnnotation.renderRow(c, t, tr); + XhtmlNode td = renderStatusRow(c, t, tr); if (hasHierarchy) { td.addText(Integer.toString(level+1)); td = tr.td(); @@ -425,9 +425,9 @@ public class CodeSystemRenderer extends TerminologyRenderer { if (c != null &&c.hasDefinitionElement()) { if (getContext().getLang() == null) { if (hasMarkdownInDefinitions(cs)) { - addMarkdown(VersionComparisonAnnotation.renderDiv(c.getDefinitionElement(), td), c.getDefinition()); + addMarkdown(renderStatusDiv(c.getDefinitionElement(), td), c.getDefinition()); } else { - VersionComparisonAnnotation.render(c.getDefinitionElement(), td).addText(c.getDefinition()); + renderStatus(c.getDefinitionElement(), td).addText(c.getDefinition()); } } else if (getContext().getLang().equals("*")) { boolean sl = false; @@ -436,9 +436,9 @@ public class CodeSystemRenderer extends TerminologyRenderer { sl = true; td.addText((sl ? cs.getLanguage("en")+": " : "")); if (hasMarkdownInDefinitions(cs)) - addMarkdown(VersionComparisonAnnotation.renderDiv(c.getDefinitionElement(), td), c.getDefinition()); + addMarkdown(renderStatusDiv(c.getDefinitionElement(), td), c.getDefinition()); else - VersionComparisonAnnotation.render(c.getDefinitionElement(), td).addText(c.getDefinition()); + renderStatus(c.getDefinitionElement(), td).addText(c.getDefinition()); for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) { td.br(); @@ -446,7 +446,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { } } } else if (getContext().getLang().equals(cs.getLanguage()) || (getContext().getLang().equals("en") && !cs.hasLanguage())) { - VersionComparisonAnnotation.render(c.getDefinitionElement(), td).addText(c.getDefinition()); + renderStatus(c.getDefinitionElement(), td).addText(c.getDefinition()); } else { for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && cd.getLanguage().equals(getContext().getLang())) { @@ -623,7 +623,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { public void renderDisplayName(ConceptDefinitionComponent c, CodeSystem cs, XhtmlNode td) { if (c.hasDisplayElement()) { if (getContext().getLang() == null) { - VersionComparisonAnnotation.render(c.getDisplayElement(), td).addText(c.getDisplay()); + renderStatus(c.getDisplayElement(), td).addText(c.getDisplay()); } else if (getContext().getLang().equals("*")) { boolean sl = false; for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) @@ -637,7 +637,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { } } } else if (getContext().getLang().equals(cs.getLanguage()) || (getContext().getLang().equals("en") && !cs.hasLanguage())) { - VersionComparisonAnnotation.render(c.getDisplayElement(), td).addText(c.getDisplay()); + renderStatus(c.getDisplayElement(), td).addText(c.getDisplay()); } else { for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(getContext().getLang())) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java index 1bb19cc21..222bad1cf 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java @@ -1,6 +1,8 @@ package org.hl7.fhir.r5.renderers; +import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; @@ -11,6 +13,7 @@ import org.hl7.fhir.utilities.StandardsStatus; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; import org.hl7.fhir.utilities.validation.ValidationOptions; +import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; /** @@ -84,4 +87,155 @@ public class Renderer extends TranslatingUtilities { } } + protected XhtmlNode renderStatus(Base b, XhtmlNode x) { + if (b == null || context.getChangeVersion() == null) { + return x; + } + VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME); + if (vca == null) { + return x; + } + switch (vca.getType()) { + case Added: + XhtmlNode spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); + XhtmlNode spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+context.getChangeVersion()); + spanInner.img("icon-change-add.png", "icon"); + spanInner.tx(" Added:"); + return spanOuter; + case Changed: + spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); + spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+vca.getOriginal()+"')" : "")); + spanInner.img("icon-change-edit.png", "icon"); + spanInner.tx(" Changed:"); + return spanOuter; + case Deleted: + spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); + spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion()); + spanInner.img("icon-change-remove.png", "icon"); + spanInner.tx(" Removed:"); + return spanOuter.strikethrough(); + default: + return x; + } + } + + protected XhtmlNode renderStatusDiv(Base b, XhtmlNode x) { + if (b == null || context.getChangeVersion() == null) { + return x; + } + VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME); + if (vca == null) { + return x; + } + switch (vca.getType()) { + case Added: + XhtmlNode divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); + XhtmlNode spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+context.getChangeVersion()); + spanInner.img("icon-change-add.png", "icon"); + spanInner.tx(" Added:"); + return divOuter; + case Changed: + divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); + spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+(vca.getOriginal())+"')" : "")); + spanInner.img("icon-change-edit.png", "icon"); + spanInner.tx(" Changed:"); + return divOuter; + case Deleted: + divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); + spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion()); + spanInner.img("icon-change-remove.png", "icon"); + spanInner.tx(" Removed:"); + return divOuter.strikethrough(); + default: + return x; + } + } + + + protected XhtmlNode renderStatusRow(Base b, XhtmlNode tbl, XhtmlNode tr) { + if (b == null || context.getChangeVersion() == null) { + return tr.td(); + } + VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME); + if (vca == null) { + return tr.td(); + } + switch (vca.getType()) { + case Added: + if (tbl.isClass("grid")) { + tr.style("border: solid 1px #dddddd; margin: 2px; padding: 2px"); + } + XhtmlNode td = tr.td(); + XhtmlNode span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+context.getChangeVersion()); + span.img("icon-change-add.png", "icon"); + span.tx(" Added:"); + XhtmlNode x = new XhtmlNode(NodeType.Element, "holder"); + x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+context.getChangeVersion()).tx(" "); + tr.styleCells(x); + return td; + case Changed: + td = tr.td(); + span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been changed since"+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+vca.getOriginal()+"')" : "")); + span.img("icon-change-edit.png", "icon"); + span.tx(" Changed:"); + return td; + case Deleted: + tr.style("text-decoration: line-through"); + td = tr.td(); + span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion()); + span.img("icon-change-remove.png", "icon"); + span.tx(" Removed:"); + x = new XhtmlNode(NodeType.Element, "holder"); + x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px; text-decoration: none", "This row of content has been added since "+context.getChangeVersion()).tx(" "); + tr.styleCells(x); + return td; + default: + return tr.td(); + } + } + +// +// +//public static CanonicalResourceComparison artifactComparison(Base base) { +// if (base.hasUserData(USER_DATA_NAME)) { +// VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME); +// return self.comp; +// } else { +// return null; +// } +//} +// +//public static void renderSummary(Base base, XhtmlNode x, String version, String... metadataFields) { +// if (base.hasUserData(USER_DATA_NAME)) { +// VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME); +// switch (self.type) { +// case Added: +// XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); +// spanInner.img("icon-change-add.png", "icon"); +// spanInner.tx(" Added"); +// return; +// case Changed: +// if (self.comp.noChangeOtherThanMetadata(metadataFields)) { +// x.span("color: #eeeeee").tx("n/c"); +// return; +// } else { +// spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version+(self.original != null ? " (was '"+(self.original.primitiveValue())+"')" : "")); +// spanInner.img("icon-change-edit.png", "icon"); +// spanInner.tx(" Changed"); +// } +// return; +// case Deleted: +// spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); +// spanInner.img("icon-change-remove.png", "icon"); +// spanInner.tx(" Removed"); +// return; +// default: +// x.span("color: #eeeeee").tx("n/c"); +// return; +// } +// } else { +// x.span("color: #eeeeee").tx("--"); +// } +//} + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index 29f121b4e..7e4c92c4d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Stack; import java.util.Map; import java.util.Set; @@ -54,6 +55,7 @@ import org.hl7.fhir.r5.model.Enumerations.BindingStrength; import org.hl7.fhir.r5.model.Extension; import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.IntegerType; +import org.hl7.fhir.r5.model.MarkdownType; import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.r5.model.Quantity; import org.hl7.fhir.r5.model.Resource; @@ -79,6 +81,7 @@ import org.hl7.fhir.utilities.StandardsStatus; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.i18n.I18nConstants; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; @@ -172,10 +175,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer { private abstract class ItemWithStatus { ListItemStatus status = ListItemStatus.New; // new, unchanged, removed - protected abstract void renderDetails(XhtmlNode f); + protected abstract void renderDetails(XhtmlNode f) throws IOException; protected abstract boolean matches(ItemWithStatus other); - public void render(XhtmlNode x) { + public void render(XhtmlNode x) throws IOException { XhtmlNode f = x; if (status == ListItemStatus.Unchanged) { f = unchanged(f); @@ -247,14 +250,25 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } public void renderDetails(XhtmlNode f) { + f = renderStatus(value, f); f.b().attribute("title", "Formal Invariant Identifier").tx(value.getKey()); f.tx(": "); - f.tx(value.getHuman()); + if (value.hasHuman()) { + renderStatus(value.getHumanElement(), f).tx(value.getHuman()); + } else if (VersionComparisonAnnotation.hasDeleted(value, "human")) { + Base b =VersionComparisonAnnotation.getDeletedItem(value, "human"); + renderStatus(b, f).tx(b.primitiveValue()); + } f.tx(" ("); if (status == ListItemStatus.New) { - f.code().tx(value.getExpression()); + if (value.hasExpression()) { + renderStatus(value.getExpressionElement(), f).code().tx(value.getExpression()); + } else if (VersionComparisonAnnotation.hasDeleted(value, "expression")) { + Base b = VersionComparisonAnnotation.getDeletedItem(value, "expression"); + renderStatus(b, f).code().tx(b.primitiveValue()); + } } else { - f.tx(value.getExpression()); + renderStatus(value.getExpressionElement(), f).tx(value.getExpression()); } f.tx(")"); } @@ -293,6 +307,27 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } f.tx(value.asStringValue()); } + + } + + private class DataValueWithStatus extends ItemWithStatus { + DataType value; + protected DataValueWithStatus(DataType value) { + this.value = value; + } + + protected boolean matches(ItemWithStatus other) { + return ((ValueWithStatus) other).value.equalsDeep(value); + } + + public void renderDetails(XhtmlNode f) throws IOException { + + if (value.hasUserData("render.link")) { + f = f.ah(value.getUserString("render.link")); + } + f.tx(summarize(value)); + } + } @@ -3060,8 +3095,6 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return null; } - - private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) { int i = ed.getSnapshot().getElement().indexOf(c) + 1; while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) { @@ -3083,6 +3116,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { generateAnchors(stack, allAnchors); checkInScope(stack, excluded); } + Stack dstack = new Stack<>(); for (ElementDefinition ec : elements) { if ((incProfiledOut || !"0".equals(ec.getMax())) && !excluded.contains(ec)) { ElementDefinition compareElement = null; @@ -3094,7 +3128,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { List anchors = makeAnchors(ec, anchorPrefix); String title = ec.getId(); XhtmlNode tr = t.tr(); - XhtmlNode sp = VersionComparisonAnnotation.render(ec, tr.td("structure").colspan(2).spanClss("self-link-parent")); + XhtmlNode sp = renderStatus(ec, tr.td("structure").colspan(2).spanClss("self-link-parent")); for (String s : anchors) { sp.an(s).tx(" "); } @@ -3116,6 +3150,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer { // generateElementInner(b, extDefn, extDefn.getSnapshot().getElement().get(0), valueDefn == null ? 2 : 3, valueDefn); } } else { + while (!dstack.isEmpty() && !isParent(dstack.peek(), ec)) { + finish(t, sd, dstack.pop(), mode); + } + dstack.push(ec); generateElementInner(t, sd, ec, mode, null, compareElement, null, false); if (ec.hasSlicing()) { generateSlicing(t, sd, ec, ec.getSlicing(), compareElement, mode, false); @@ -3125,20 +3163,26 @@ public class StructureDefinitionRenderer extends ResourceRenderer { t.tx("\r\n"); i++; } - for (Base b : VersionComparisonAnnotation.getDeleted(sd, "element")) { + while (!dstack.isEmpty()) { + finish(t, sd, dstack.pop(), mode); + } + finish(t, sd, null, mode); + } + + private void finish(XhtmlNode t, StructureDefinition sd, ElementDefinition ed, int mode) throws FHIRException, IOException { + + for (Base b : VersionComparisonAnnotation.getDeleted(ed == null ? sd : ed, "element")) { ElementDefinition ec = (ElementDefinition) b; String title = ec.getId(); XhtmlNode tr = t.tr(); - XhtmlNode sp = VersionComparisonAnnotation.render(ec, tr.td("structure").colspan(2).spanClss("self-link-parent")); - sp.span("color: grey", null).tx(Integer.toString(i++)); + XhtmlNode sp = renderStatus(ec, tr.td("structure").colspan(2).spanClss("self-link-parent")); + sp.span("color: grey", null).tx("--"); sp.b().tx(". "+title); generateElementInner(t, sd, ec, mode, null, null, null, true); if (ec.hasSlicing()) { generateSlicing(t, sd, ec, ec.getSlicing(), null, mode, true); - } - - t.tx("\r\n"); + } } } @@ -3310,7 +3354,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { if (md.hasValue()) { String xhtml = hostMd.processMarkdown(location, md); XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); - VersionComparisonAnnotation.renderDiv(md, x).add(new XhtmlParser().parseFragment(xhtml)); + renderStatusDiv(md, x).add(new XhtmlParser().parseFragment(xhtml)); return x; } else { return null; @@ -3369,10 +3413,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer { XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); if (mode != GEN_MODE_KEY) { if (newStr != null) { - VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr); + renderStatus(source, x).ah(nLink).tx(newStr); } else if (VersionComparisonAnnotation.hasDeleted(parent, name)) { PrimitiveType p = (PrimitiveType) VersionComparisonAnnotation.getDeletedItem(parent, name); - VersionComparisonAnnotation.render(p, x).tx(p.primitiveValue()); + renderStatus(p, x).tx(p.primitiveValue()); } else { return null; } @@ -3380,7 +3424,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { if (newStr==null || newStr.isEmpty()) { return null; } else { - VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr); + renderStatus(source, x).ah(nLink).tx(newStr); } } else if (oldStr!=null && !oldStr.isEmpty() && (newStr==null || newStr.isEmpty())) { if (mode == GEN_MODE_DIFF) { @@ -3396,10 +3440,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } } else if (newStr.startsWith(oldStr)) { unchanged(x).ah(oLink).tx(oldStr); - VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr.substring(oldStr.length())); + renderStatus(source, x).ah(nLink).tx(newStr.substring(oldStr.length())); } else { // TODO: improve comparision in this fall-through case, by looking for matches in sub-paragraphs? - VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr); + renderStatus(source, x).ah(nLink).tx(newStr); removed(x).ah(oLink).tx(oldStr); } return x; @@ -3432,21 +3476,25 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } private void generateElementInner(XhtmlNode tbl, StructureDefinition sd, ElementDefinition d, int mode, ElementDefinition value, ElementDefinition compare, ElementDefinition compareValue, boolean strikethrough) throws FHIRException, IOException { + System.out.println(d.getPath()); boolean root = !d.getPath().contains("."); boolean slicedExtension = d.hasSliceName() && (d.getPath().endsWith(".extension") || d.getPath().endsWith(".modifierExtension")); // int slicedExtensionMode = (mode == GEN_MODE_KEY) && slicedExtension ? GEN_MODE_SNAP : mode; // see ProfileUtilities.checkExtensionDoco / Task 3970 if (d.hasSliceName()) { - tableRow(tbl, "SliceName", "profiling.html#slicing", strikethrough).tx(d.getSliceName()); + tableRow(tbl, "Slice Name", "profiling.html#slicing", strikethrough, compareString(d.getSliceName(), d.getSliceNameElement(), null, (compare != null ? compare.getSliceName() : null), d, null, "sliceName", mode)); + tableRow(tbl, "Slice Constraining", "profiling.html#slicing", strikethrough, compareString(encodeValue(d.getSliceIsConstrainingElement()), d.getSliceIsConstrainingElement(), null, (compare != null ? encodeValue(compare.getSliceIsConstrainingElement()) : null), d, null, "sliceName", mode)); } + tableRow(tbl, "Definition", null, strikethrough, compareMarkdown(sd.getName(), d.getDefinitionElement(), (compare==null) || slicedExtension ? null : compare.getDefinitionElement(), mode)); tableRow(tbl, "Short", null, strikethrough, compareString(d.hasShort() ? d.getShort() : null, d.getShortElement(), null, "short", d, compare!= null && compare.hasShortElement() ? compare.getShort() : null, null, mode)); + tableRow(tbl, "Comments", null, strikethrough, compareMarkdown(sd.getName(), d.getCommentElement(), (compare==null) || slicedExtension ? null : compare.getCommentElement(), mode)); tableRow(tbl, "Note", null, strikethrough, businessIdWarning(sd.getName(), tail(d.getPath()))); tableRow(tbl, "Control", "conformance-rules.html#conformance", strikethrough, describeCardinality(d, compare, mode)); tableRow(tbl, "Binding", "terminologies.html", strikethrough, describeBinding(sd, d, d.getPath(), compare, mode)); if (d.hasContentReference()) { tableRow(tbl, "Type", null, strikethrough, "See " + d.getContentReference().substring(1)); } else { - tableRow(tbl, "Type", "datatypes.html", strikethrough, describeTypes(d.getType(), false, compare, mode, value, compareValue, sd)); + tableRow(tbl, "Type", "datatypes.html", strikethrough, describeTypes(d.getType(), false, d, compare, mode, value, compareValue, sd)); } if (d.hasExtension(ToolingExtensions.EXT_DEF_TYPE)) { tableRow(tbl, "Default Type", "datatypes.html", strikethrough, ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_DEF_TYPE)); @@ -3457,7 +3505,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { if (d.getPath().endsWith("[x]") && !d.prohibited()) { tableRow(tbl, "[x] Note", null, strikethrough).ahWithText("See ", spec("formats.html#choice"), null, "Choice of Data Types", " for further information about how to use [x]"); } - tableRow(tbl, "Is Modifier", "conformance-rules.html#ismodifier", strikethrough, displayBoolean(d.getIsModifier(), d.getIsModifierElement(), "isModifier", d, null, mode)); + tableRow(tbl, "Is Modifier", "conformance-rules.html#ismodifier", strikethrough, presentModifier(d, mode, compare)); if (d.getMustHaveValue()) { tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", strikethrough, "This primitive type must have a value (the value must be present, and cannot be replaced by an extension)"); } else if (d.hasValueAlternatives()) { @@ -3471,7 +3519,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { tableRow(tbl, "Must Support", "conformance-rules.html#mustSupport", strikethrough, displayBoolean(d.getMustSupport(), d.getMustSupportElement(), "mustSupport", d, compare==null ? null : compare.getMustSupportElement(), mode)); if (d.getMustSupport()) { if (hasMustSupportTypes(d.getType())) { - tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, describeTypes(d.getType(), true, compare, mode, null, null, sd)); + tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, describeTypes(d.getType(), true, d, compare, mode, null, null, sd)); } else if (hasChoices(d.getType())) { tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, "No must-support rules about the choice of types/profiles"); } @@ -3552,19 +3600,37 @@ public class StructureDefinitionRenderer extends ResourceRenderer { tableRow(tbl, "Summary", "search.html#summary", strikethrough, Boolean.toString(d.getIsSummary())); } tableRow(tbl, "Requirements", null, strikethrough, compareMarkdown(sd.getName(), d.getRequirementsElement(), (compare==null) || slicedExtension ? null : compare.getRequirementsElement(), mode)); + tableRow(tbl, "Label", null, strikethrough, compareString(d.getLabel(), d.getLabelElement(), null, "label", d, (compare != null ? compare.getLabel() : null), null, mode)); tableRow(tbl, "Alternate Names", null, strikethrough, compareSimpleTypeLists(d.getAlias(), ((compare==null) || slicedExtension ? null : compare.getAlias()), mode)); - tableRow(tbl, "Comments", null, strikethrough, compareMarkdown(sd.getName(), d.getCommentElement(), (compare==null) || slicedExtension ? null : compare.getCommentElement(), mode)); + tableRow(tbl, "Definitional Codes", null, strikethrough, compareDataTypeLists(d.getCode(), ((compare==null) || slicedExtension ? null : compare.getCode()), mode)); + tableRow(tbl, "Min Value", null, strikethrough, compareString(d.hasMinValue() ? encodeValue(d.getMinValue()) : null, d.getMinValue(), null, "minValue", d, compare!= null && compare.hasMinValue() ? encodeValue(compare.getMinValue()) : null, null, mode)); + tableRow(tbl, "Max Value", null, strikethrough, compareString(d.hasMaxValue() ? encodeValue(d.getMaxValue()) : null, d.getMaxValue(), null, "maxValue", d, compare!= null && compare.hasMaxValue() ? encodeValue(compare.getMaxValue()) : null, null, mode)); tableRow(tbl, "Max Length", null, strikethrough, compareString(d.hasMaxLength() ? toStr(d.getMaxLength()) : null, d.getMaxLengthElement(), null, "maxLength", d, compare!= null && compare.hasMaxLengthElement() ? toStr(compare.getMaxLength()) : null, null, mode)); + tableRow(tbl, "Value Required", null, strikethrough, compareString(encodeValue(d.getMustHaveValueElement()), d.getMustHaveValueElement(), null, (compare != null ? encodeValue(compare.getMustHaveValueElement()) : null), d, null, "mustHaveValueElement", mode)); + tableRow(tbl, "Value Alternatives", null, strikethrough, compareSimpleTypeLists(d.getValueAlternatives(), ((compare==null) || slicedExtension ? null : compare.getValueAlternatives()), mode)); tableRow(tbl, "Default Value", null, strikethrough, encodeValue(d.getDefaultValue(), "defaultValue", d, compare==null ? null : compare.getDefaultValue(), mode)); tableRow(tbl, "Meaning if Missing", null, strikethrough, d.getMeaningWhenMissing()); tableRow(tbl, "Fixed Value", null, strikethrough, encodeValue(d.getFixed(), "fixed", d, compare==null ? null : compare.getFixed(), mode)); tableRow(tbl, "Pattern Value", null, strikethrough, encodeValue(d.getPattern(), "pattern", d, compare==null ? null : compare.getPattern(), mode)); tableRow(tbl, "Example", null, strikethrough, encodeValues(d.getExample())); - tableRow(tbl, "Invariants", null, strikethrough, invariants(d.getConstraint(), compare==null ? null : compare.getConstraint(), mode)); + tableRow(tbl, "Invariants", null, strikethrough, invariants(d.getConstraint(), compare==null ? null : compare.getConstraint(), d, mode)); tableRow(tbl, "LOINC Code", null, strikethrough, getMapping(sd, d, LOINC_MAPPING, compare, mode)); tableRow(tbl, "SNOMED-CT Code", null, strikethrough, getMapping(sd, d, SNOMED_MAPPING, compare, mode)); + tbl.tx("\r\n"); } - + + private XhtmlNode presentModifier(ElementDefinition d, int mode, ElementDefinition compare) throws FHIRException, IOException { + XhtmlNode x1 = compareString(encodeValue(d.getIsModifierElement()), d.getIsModifierElement(), null, "isModifier", d, compare == null ? null : encodeValue(compare.getIsModifierElement()), null, mode); + if (x1 != null) { + XhtmlNode x2 = compareString(encodeValue(d.getIsModifierReasonElement()), d.getIsModifierReasonElement(), null, "isModifierReason", d, compare == null ? null : encodeValue(compare.getIsModifierReasonElement()), null, mode); + if (x2 != null) { + x1.tx(" because "); + x1.copyAllContent(x2); + } + } + return x1; + } + private String spec(String name) { return Utilities.pathURL(VersionUtilities.getSpecUrl(context.getWorker().getVersion()) , name); } @@ -3817,6 +3883,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { x.tx(", and defines no discriminators to differentiate the slices"); } tableRow(tbl, "Slicing", "profiling.html#slicing", strikethrough, x); + tbl.tx("\r\n"); } private XhtmlNode tableRow(XhtmlNode x, String name, String defRef, boolean strikethrough) throws IOException { @@ -3896,18 +3963,18 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return null; } - private XhtmlNode describeCardinality(ElementDefinition d, ElementDefinition compare, int mode) { + private XhtmlNode describeCardinality(ElementDefinition d, ElementDefinition compare, int mode) throws IOException { XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); if (compare==null || mode==GEN_MODE_DIFF) { if (!d.hasMax() && !d.hasMin()) return null; else if (d.getMax() == null) { - VersionComparisonAnnotation.render(d.getMinElement(), x).tx(toStr(d.getMin())); + renderStatus(d.getMinElement(), x).tx(toStr(d.getMin())); x.tx("..?"); } else { - VersionComparisonAnnotation.render(d.getMinElement(), x).tx(toStr(d.getMin())); + renderStatus(d.getMinElement(), x).tx(toStr(d.getMin())); x.tx( ".."); - VersionComparisonAnnotation.render(d.getMaxElement(), x).tx( d.getMax()); + renderStatus(d.getMaxElement(), x).tx( d.getMax()); } } else { if (!(mode==GEN_MODE_DIFF && (d.getMin()==compare.getMin() || d.getMin()==0))) { @@ -3936,19 +4003,21 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return false; } - private XhtmlNode describeTypes(List types, boolean mustSupportOnly, ElementDefinition compare, int mode, ElementDefinition value, ElementDefinition compareValue, StructureDefinition sd) throws FHIRException { + private XhtmlNode describeTypes(List types, boolean mustSupportOnly, ElementDefinition ed, ElementDefinition compare, int mode, ElementDefinition value, ElementDefinition compareValue, StructureDefinition sd) throws FHIRException, IOException { if (types.isEmpty()) return null; List compareTypes = compare==null ? new ArrayList<>() : compare.getType(); XhtmlNode ret = new XhtmlNode(NodeType.Element, "div"); - if ((!mustSupportOnly && types.size() == 1 && compareTypes.size() <=1) || (mustSupportOnly && mustSupportCount(types) == 1)) { + if ((!mustSupportOnly && types.size() == 1 && compareTypes.size() <=1 && (mode != GEN_MODE_DIFF || !VersionComparisonAnnotation.hasDeleted(ed, "type"))) || (mustSupportOnly && mustSupportCount(types) == 1)) { if (!mustSupportOnly || isMustSupport(types.get(0))) { describeType(ret, types.get(0), mustSupportOnly, compareTypes.size()==0 ? null : compareTypes.get(0), mode, sd); } } else { boolean first = true; - ret.tx("Choice of: "); + if (types.size() > 1) { + ret.tx("Choice of: "); + } Map map = new HashMap(); for (TypeRefComponent t : compareTypes) { map.put(t.getCode(), t); @@ -3970,6 +4039,13 @@ public class StructureDefinitionRenderer extends ResourceRenderer { ret.tx(", "); describeType(removed(ret), t, mustSupportOnly, null, mode, sd); } + if (mode == GEN_MODE_DIFF) { + for (Base b : VersionComparisonAnnotation.getDeleted(ed, "type")) { + TypeRefComponent t = (TypeRefComponent) b; + ret.tx(", "); + describeType(ret, t, false, null, mode, sd); + } + } } if (value != null) { XhtmlNode xt = processSecondary(mode, value, compareValue, mode, sd); @@ -3980,7 +4056,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return ret; } - private XhtmlNode processSecondary(int mode, ElementDefinition value, ElementDefinition compareValue, int compMode, StructureDefinition sd) throws FHIRException { + private XhtmlNode processSecondary(int mode, ElementDefinition value, ElementDefinition compareValue, int compMode, StructureDefinition sd) throws FHIRException, IOException { switch (mode) { case 1: return null; @@ -3991,7 +4067,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { case 3: x = new XhtmlNode(NodeType.Element, "div"); x.tx(" (Extension Type: "); - x.copyAllContent(describeTypes(value.getType(), false, compareValue, compMode, null, null, sd)); + x.copyAllContent(describeTypes(value.getType(), false, value, compareValue, compMode, null, null, sd)); x.tx(")"); return x; default: @@ -4011,7 +4087,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } - private void describeType(XhtmlNode x, TypeRefComponent t, boolean mustSupportOnly, TypeRefComponent compare, int mode, StructureDefinition sd) throws FHIRException { + private void describeType(XhtmlNode x, TypeRefComponent t, boolean mustSupportOnly, TypeRefComponent compare, int mode, StructureDefinition sd) throws FHIRException, IOException { if (t.getWorkingCode() == null) { return; } @@ -4021,9 +4097,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer { boolean ts = false; if (t.getWorkingCode().startsWith("xs:")) { - ts = compareString(x, t.getWorkingCode(), t.getCodeElement(), null, "code", t, compare==null ? null : compare.getWorkingCode(), null, mode); + ts = compareString(x, t.getWorkingCode(), t, null, "code", t, compare==null ? null : compare.getWorkingCode(), null, mode); } else { - ts = compareString(x, t.getWorkingCode(), t.getCodeElement(), getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode); + ts = compareString(x, t.getWorkingCode(), t, getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode); } if ((!mustSupportOnly && (t.hasProfile() || (compare!=null && compare.hasProfile()))) || isMustSupport(t.getProfile())) { @@ -4140,7 +4216,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } - private XhtmlNode invariants(List originalList, List compareList, int mode) { + private XhtmlNode invariants(List originalList, List compareList, ElementDefinition parent, int mode) throws IOException { StatusList list = new StatusList<>(); for (ElementDefinitionConstraintComponent v : originalList) { if (!v.isEmpty()) { @@ -4161,6 +4237,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer { if (first) first = false; else x.br(); t.render(x); } + for (Base b : VersionComparisonAnnotation.getDeleted(parent, "invariant")) { + if (first) first = false; else x.br(); + InvariantWithStatus ts = new InvariantWithStatus((ElementDefinitionConstraintComponent) b); + ts.render(x); + } return x; } @@ -4172,7 +4253,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { ElementDefinitionBindingComponent compBinding = compare == null ? null : compare.getBinding(); XhtmlNode bindingDesc = null; if (binding.hasDescription()) { - StringType newBinding = PublicationHacker.fixBindingDescriptions(context.getContext(), binding.getDescriptionElement()); + MarkdownType newBinding = PublicationHacker.fixBindingDescriptions(context.getContext(), binding.getDescriptionElement()); if (mode == GEN_MODE_SNAP || mode == GEN_MODE_MS) { bindingDesc = new XhtmlNode(NodeType.Element, "div"); bindingDesc.add(new XhtmlParser().parseFragment(hostMd.processMarkdown("Binding.description", newBinding))); @@ -4181,22 +4262,13 @@ public class StructureDefinitionRenderer extends ResourceRenderer { bindingDesc = compareMarkdown("Binding.description", newBinding, oldBinding, mode); } } - if (!binding.hasValueSet()) + if (!binding.hasValueSet()) { return bindingDesc; + } XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); var nsp = x.span(); - renderBinding(nsp, binding, path, sd); - if (compBinding!=null ) { - var osp = x.span(); - renderBinding(osp, compBinding, path, sd); - if (osp.allText().equals(nsp.allText())) { - nsp.style(unchangedStyle()); - x.remove(osp); - } else { - osp.style(removedStyle()); - } - } + renderBinding(nsp, binding, compBinding, path, sd, mode); if (bindingDesc != null) { if (isSimpleContent(bindingDesc)) { x.tx(": "); @@ -4232,16 +4304,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return bindingDesc.getChildNodes().size() == 1 && bindingDesc.getChildNodes().get(0).isPara(); } - private void renderBinding(XhtmlNode span, ElementDefinitionBindingComponent binding, String path, StructureDefinition sd) { + private void renderBinding(XhtmlNode span, ElementDefinitionBindingComponent binding, ElementDefinitionBindingComponent compare, String path, StructureDefinition sd, int mode) { + compareString(span, conf(binding), binding.getStrengthElement(), null, "strength", binding, compare == null ? null : conf(compare), null, mode); + span.tx(" "); BindingResolution br = context.getPkp().resolveBinding(sd, binding, path); - span.tx(conf(binding)); - if (br.url == null) { - span.code().tx(br.display); - } else { - span.ah(br.url).tx(br.display); - } - span.tx(confTail(binding)); - + compareString(span, br.display, binding.getValueSetElement(), br.url, "valueSet", binding, compare == null ? null : compare.getValueSet(), null, mode); } private String stripPara(String s) { @@ -4254,13 +4321,6 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return s; } - private String confTail(ElementDefinitionBindingComponent def) { - if (def.getStrength() == BindingStrength.EXTENSIBLE) - return "; other codes may be used where these codes are not suitable"; - else - return ""; - } - private String conf(ElementDefinitionBindingComponent def) { if (def.getStrength() == null) { return "For codes, see "; @@ -4271,7 +4331,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { case PREFERRED: return "The codes SHOULD be taken from "; case EXTENSIBLE: - return "The codes SHALL be taken from "; + return "Unless not suitable, these codes SHALL be taken from "; case REQUIRED: return "The codes SHALL be taken from "; default: @@ -4348,12 +4408,12 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return compareString(Utilities.escapeXml(newMap), null, null, "mapping", d, Utilities.escapeXml(oldMap), null, mode); } - private XhtmlNode compareSimpleTypeLists(List original, List compare, int mode) { + private XhtmlNode compareSimpleTypeLists(List original, List compare, int mode) throws IOException { return compareSimpleTypeLists(original, compare, mode, ", "); } - private XhtmlNode compareSimpleTypeLists(List originalList, List compareList, int mode, String separator) { + private XhtmlNode compareSimpleTypeLists(List originalList, List compareList, int mode, String separator) throws IOException { StatusList list = new StatusList<>(); for (PrimitiveType v : originalList) { if (!v.isEmpty()) { @@ -4378,6 +4438,37 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } + private XhtmlNode compareDataTypeLists(List original, List compare, int mode) throws IOException { + return compareDataTypeLists(original, compare, mode, ", "); + } + + + private XhtmlNode compareDataTypeLists(List originalList, List compareList, int mode, String separator) throws IOException { + StatusList list = new StatusList<>(); + for (DataType v : originalList) { + if (!v.isEmpty()) { + list.add(new DataValueWithStatus(v)); + } + } + if (compareList != null && mode != GEN_MODE_DIFF) { + for (DataType v : compareList) { + list.merge(new DataValueWithStatus(v)); + } + } + if (list.size() == 0) { + return null; + } + XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); + boolean first = true; + for (DataValueWithStatus t : list) { + if (first) first = false; else x.tx(separator); + t.render(x); + } + return x; + } + + + private String summarise(CodeableConcept cc) throws FHIRException { if (cc.getCoding().size() == 1 && cc.getText() == null) { return summarise(cc.getCoding().get(0)); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java index 3427e158d..291a24c3a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java @@ -1146,7 +1146,7 @@ public class ValueSetRenderer extends TerminologyRenderer { boolean hasExtensions = false; XhtmlNode li; li = ul.li(); - li = VersionComparisonAnnotation.render(inc, li); + li = renderStatus(inc, li); Map definitions = new HashMap<>(); @@ -1199,7 +1199,7 @@ public class ValueSetRenderer extends TerminologyRenderer { li.tx(", "); } } - XhtmlNode wli = VersionComparisonAnnotation.render(f, li); + XhtmlNode wli = renderStatus(f, li); if (f.getOp() == FilterOperator.EXISTS) { if (f.getValue().equals("true")) { wli.tx(f.getProperty()+" exists"); @@ -1239,7 +1239,7 @@ public class ValueSetRenderer extends TerminologyRenderer { first = false; else li.tx(", "); - XhtmlNode wli = VersionComparisonAnnotation.render(vs, li); + XhtmlNode wli = renderStatus(vs, li); AddVsRef(vs.asStringValue(), wli, vsRes); } } @@ -1256,13 +1256,13 @@ public class ValueSetRenderer extends TerminologyRenderer { first = false; else li.tx(", "); - XhtmlNode wli = VersionComparisonAnnotation.render(vs, li); + XhtmlNode wli = renderStatus(vs, li); AddVsRef(vs.asStringValue(), wli, vsRes); } } else { XhtmlNode xul = li.ul(); for (UriType vs : inc.getValueSet()) { - XhtmlNode wli = VersionComparisonAnnotation.render(vs, xul.li()); + XhtmlNode wli = renderStatus(vs, xul.li()); AddVsRef(vs.asStringValue(), wli, vsRes); } @@ -1275,16 +1275,16 @@ public class ValueSetRenderer extends TerminologyRenderer { List maps, Map designations, Map definitions, XhtmlNode t, boolean hasComments, boolean hasDefinition, ConceptReferenceComponent c) { XhtmlNode tr = t.tr(); - XhtmlNode td = VersionComparisonAnnotation.renderRow(c, t, tr); + XhtmlNode td = renderStatusRow(c, t, tr); ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode()); addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td); td = tr.td(); if (!Utilities.noString(c.getDisplay())) - VersionComparisonAnnotation.render(c.getDisplayElement(), td).addText(c.getDisplay()); + renderStatus(c.getDisplayElement(), td).addText(c.getDisplay()); else if (VersionComparisonAnnotation.hasDeleted(c, "display")) { StringType d = (StringType) VersionComparisonAnnotation.getDeletedItem(c, "display"); - VersionComparisonAnnotation.render(d, td).addText(d.primitiveValue()); + renderStatus(d, td).addText(d.primitiveValue()); } else if (cc != null && !Utilities.noString(cc.getDisplay())) td.style("color: #cccccc").addText(cc.getDisplay()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java index 932bc44ea..529c2e2cb 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java @@ -187,6 +187,7 @@ public class RenderingContext { private DateTimeFormatter dateYearMonthFormat; private boolean copyButton; private ProfileKnowledgeProvider pkp; + private String changeVersion; private Map links = new HashMap<>(); /** @@ -709,4 +710,14 @@ public class RenderingContext { return this; } + public String getChangeVersion() { + return changeVersion; + } + + public RenderingContext setChangeVersion(String changeVersion) { + this.changeVersion = changeVersion; + return this; + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java index f1c27c413..cd30eae1a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java @@ -315,9 +315,12 @@ public class CompareUtilities extends BaseTestingUtilities { } private String compareNodes(String path, JsonElement expectedJsonElement, JsonElement actualJsonElement) { - if (actualJsonElement.getClass() != expectedJsonElement.getClass()) - return createNotEqualMessage("properties differ at " + path, expectedJsonElement.getClass().getName(), actualJsonElement.getClass().getName()); - else if (actualJsonElement instanceof JsonPrimitive) { + if (!(expectedJsonElement instanceof JsonPrimitive && actualJsonElement instanceof JsonPrimitive)) { + if (actualJsonElement.getClass() != expectedJsonElement.getClass()) { + return createNotEqualMessage("properties differ at " + path, expectedJsonElement.getClass().getName(), actualJsonElement.getClass().getName()); + } + } + if (actualJsonElement instanceof JsonPrimitive) { JsonPrimitive actualJsonPrimitive = (JsonPrimitive) actualJsonElement; JsonPrimitive expectedJsonPrimitive = (JsonPrimitive) expectedJsonElement; if (actualJsonPrimitive.isJsonBoolean() && expectedJsonPrimitive.isJsonBoolean()) { @@ -333,8 +336,11 @@ public class CompareUtilities extends BaseTestingUtilities { } else if (actualJsonPrimitive.isJsonNumber() && expectedJsonPrimitive.isJsonNumber()) { if (!actualJsonPrimitive.asString().equals(expectedJsonPrimitive.asString())) return createNotEqualMessage("number property values differ at " + path, expectedJsonPrimitive.asString(), actualJsonPrimitive.asString()); - } else + } else if (expectedJsonElement instanceof JsonNull) { + return actualJsonPrimitive instanceof JsonNull ? null : createNotEqualMessage("null Properties differ at " + path, "null", actualJsonPrimitive.asString()); + } else { return createNotEqualMessage("property types differ at " + path, expectedJsonPrimitive.asString(), actualJsonPrimitive.asString()); + } } else if (actualJsonElement instanceof JsonObject) { String s = compareObjects(path, (JsonObject) expectedJsonElement, (JsonObject) actualJsonElement); if (!Utilities.noString(s)) @@ -363,8 +369,7 @@ public class CompareUtilities extends BaseTestingUtilities { c++; } } - } else if (actualJsonElement instanceof JsonNull) { - + } else return "unhandled property " + actualJsonElement.getClass().getName(); return null; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java index caf18522e..d6ef91547 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java @@ -151,11 +151,18 @@ public class DefinitionNavigator { if (nameMap.containsKey(path)) { DefinitionNavigator master = nameMap.get(path); ElementDefinition cm = master.current(); - // if (!cm.hasSlicing()) - // throw new DefinitionException("Found slices with no slicing details at "+dn.current().getPath()); - if (master.slices == null) - master.slices = new ArrayList(); - master.slices.add(dn); + if (diff && cm.hasSliceName()) { + // slice name - jumped straight into slicing + children.add(dn); + } else { + if (!cm.hasSlicing()) { + throw new DefinitionException("Found slices with no slicing details at "+dn.current().getPath()); + } + if (master.slices == null) { + master.slices = new ArrayList(); + } + master.slices.add(dn); + } } else { nameMap.put(path, dn); children.add(dn); @@ -238,7 +245,11 @@ public class DefinitionNavigator { @Override public String toString() { - return current().getId(); + return getId(); + } + + public String getId() { + return current() == null ? path : current().hasSliceName() ? current().getPath()+":"+current().getSliceName() : current().getPath(); } public Base parent() { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/PublicationHacker.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/PublicationHacker.java index f45f701fc..b5182d222 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/PublicationHacker.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/PublicationHacker.java @@ -1,6 +1,7 @@ package org.hl7.fhir.r5.utils; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.MarkdownType; import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; @@ -11,17 +12,21 @@ public class PublicationHacker { // this routine fixes up broken binding descriptions from past FHIR publications. All of them will be or are fixed in a later version, // but fixing old versions is procedurally very difficult. Hence, these work around fixes here - public static StringType fixBindingDescriptions(IWorkerContext context, StringType s) { - StringType res = s.copy(); - - // ServiceRequest.code - if (res.getValue().contains("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]")) { - res.setValue(res.getValue().replace("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]", "LOINC is [preferred]("+Utilities.pathURL(VersionUtilities.getSpecUrl(context.getVersion()), "terminologies.html#preferred)"))); + public static MarkdownType fixBindingDescriptions(IWorkerContext context, MarkdownType md) { + MarkdownType ret = null; + + // ServiceRequest.code + if (md.getValue().contains("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]")) { + ret = md.copy(); + ret.setValue(md.getValue().replace("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]", "LOINC is [preferred]("+Utilities.pathURL(VersionUtilities.getSpecUrl(context.getVersion()), "terminologies.html#preferred)"))); + } + if (md.getValue().contains("[here](valueset-diagnostic-requests.html)")) { + if (ret == null) { + ret = md.copy(); } - if (res.getValue().contains("[here](valueset-diagnostic-requests.html)")) { - res.setValue(res.getValue().replace("[here](valueset-diagnostic-requests.html)", "here")); - } - return res; + ret.setValue(md.getValue().replace("[here](valueset-diagnostic-requests.html)", "here")); + } + return ret == null ? md : ret; } } diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json.error index 656af6e10..0eb3e1fbd 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json.error @@ -1,3 +1,3 @@ string property values differ at .expectedArray[0] -Expected :expectedValue 1 -Actual :unexpectedValue 1 \ No newline at end of file +Expected :"expectedValue 1" +Actual :"unexpectedValue 1" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json.error index 5ad083e15..410eb777a 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json.error @@ -1,3 +1,3 @@ array item count differs at .expectedArray -Expected :2 -Actual :1 \ No newline at end of file +Expected :"2" +Actual :"1" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json.error index 3f541ed8e..30eebd9dc 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json.error @@ -1,3 +1,3 @@ boolean property values differ at .expectedBoolean -Expected :true -Actual :false \ No newline at end of file +Expected :"true" +Actual :"false" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json.error index 10722946a..dbb401ee7 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json.error @@ -1,3 +1,3 @@ number property values differ at .expectedNumber -Expected :123 -Actual :789 \ No newline at end of file +Expected :"123" +Actual :"789" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json.error index 43c2e86b1..5a64699a7 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json.error @@ -1,3 +1,3 @@ property types differ at .expectedString -Expected :expected value -Actual :1 \ No newline at end of file +Expected :"expected value" +Actual :"1" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json.error index 9e0f9eb92..8c8a7e88d 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json.error @@ -1,3 +1,3 @@ string property values differ at .expectedString -Expected :expected value -Actual :unexpected value \ No newline at end of file +Expected :"expected value" +Actual :"unexpected value" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml.error index c027d4bf6..316b81761 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml.error @@ -1,3 +1,3 @@ Attributes differ at /root/blah -Expected :dummyAtt -Actual :wrongwrongwrong \ No newline at end of file +Expected :"dummyAtt" +Actual :"wrongwrongwrong" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml.error index 51e12abf9..35bfdebce 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml.error @@ -1,3 +1,3 @@ Names differ at /root -Expected :nameSpacedNode -Actual :wrongNameSpacedNode \ No newline at end of file +Expected :"nameSpacedNode" +Actual :"wrongNameSpacedNode" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml.error index 4c5341706..52307ac8a 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml.error @@ -1,3 +1,3 @@ Namespaces differ at /root -Expected :http://www.example.com/FOO -Actual :http://www.example.com/BAR \ No newline at end of file +Expected :"http://www.example.com/FOO" +Actual :"http://www.example.com/BAR" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml.error index 3236aba11..ddbd95ec2 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml.error @@ -1,3 +1,3 @@ node type mismatch in children of /root/blah -Expected :1 -Actual :1 \ No newline at end of file +Expected :"1" +Actual :"1" \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml.error index 77e06731b..a4cdfb34d 100644 --- a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml.error +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml.error @@ -1,3 +1,3 @@ Text differs at /root/blah -Expected :expected -Actual :different \ No newline at end of file +Expected :"expected" +Actual :"different" \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java index 4f833e4ff..8ec4c7b1c 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java @@ -154,12 +154,14 @@ public class ComparisonTests { ComparisonSession session = new ComparisonSession(context, context, "Comparison Tests", null, null); if (content.has("version")) { - session.setForVersion(content.getJsonObject("version").asString("stated")); session.setAnnotate(true); } RenderingContext lrc = new RenderingContext(context, new MarkDownProcessor(Dialect.COMMON_MARK), null, "http://hl7.org/fhir", "", "en", ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER); lrc.setDestDir(Utilities.path("[tmp]", "comparison")); lrc.setPkp(new TestProfileKnowledgeProvider(context)); + if (content.has("version")) { + lrc.setChangeVersion(content.getJsonObject("version").asString("stated")); + } if (left instanceof CodeSystem && right instanceof CodeSystem) { CodeSystemComparer cs = new CodeSystemComparer(session); @@ -205,14 +207,14 @@ public class ComparisonTests { TextFile.stringToFile(HEADER + hd("Messages") + xmle + BREAK + hd("Metadata") + xml1 + BREAK + hd("Structure") + xml2 + FOOTER, Utilities.path("[tmp]", "comparison", name + ".html")); checkOutcomes(csc.getMessages(), content); - lrc.setStructureMode(StructureDefinitionRendererMode.SUMMARY); - new StructureDefinitionRenderer(lrc).render(right); - checkOutput(content.getJsonObject("version").asString("filename-tree"), right); - lrc.setStructureMode(StructureDefinitionRendererMode.DATA_DICT); new StructureDefinitionRenderer(lrc).render(right); checkOutput(content.getJsonObject("version").asString("filename-dd"), right); + + lrc.setStructureMode(StructureDefinitionRendererMode.SUMMARY); + new StructureDefinitionRenderer(lrc).render(right); + checkOutput(content.getJsonObject("version").asString("filename-tree"), right); } else if (left instanceof CapabilityStatement && right instanceof CapabilityStatement) { CapabilityStatementComparer pc = new CapabilityStatementComparer(session); CapabilityStatementComparison csc = pc.compare((CapabilityStatement) left, (CapabilityStatement) right); From f7c606e88b23a73348137eb83294ccf219161ca8 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 17 Aug 2023 18:29:59 +1000 Subject: [PATCH 03/13] rendering fixes for IGs --- .../StructureDefinitionComparer.java | 1 - .../VersionComparisonAnnotation.java | 13 ++++ .../org/hl7/fhir/r5/renderers/Renderer.java | 75 ++++++++----------- .../StructureDefinitionRenderer.java | 1 - 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructureDefinitionComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructureDefinitionComparer.java index cf816826c..8fbc6f7b4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructureDefinitionComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/StructureDefinitionComparer.java @@ -392,7 +392,6 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple boolean def = false; boolean ch = false; - System.out.println(left.getId()); // not allowed to be different: // ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java index 5ed742f33..f8acb936d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java @@ -124,4 +124,17 @@ public class VersionComparisonAnnotation { return result.isEmpty() ? null : result.get(0); } + + + + public static CanonicalResourceComparison artifactComparison(Base base) { + if (base.hasUserData(USER_DATA_NAME)) { + VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME); + return self.comp; + } else { + return null; + } + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java index 222bad1cf..b555b6867 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java @@ -194,48 +194,37 @@ public class Renderer extends TranslatingUtilities { } } -// -// -//public static CanonicalResourceComparison artifactComparison(Base base) { -// if (base.hasUserData(USER_DATA_NAME)) { -// VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME); -// return self.comp; -// } else { -// return null; -// } -//} -// -//public static void renderSummary(Base base, XhtmlNode x, String version, String... metadataFields) { -// if (base.hasUserData(USER_DATA_NAME)) { -// VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME); -// switch (self.type) { -// case Added: -// XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); -// spanInner.img("icon-change-add.png", "icon"); -// spanInner.tx(" Added"); -// return; -// case Changed: -// if (self.comp.noChangeOtherThanMetadata(metadataFields)) { -// x.span("color: #eeeeee").tx("n/c"); -// return; -// } else { -// spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version+(self.original != null ? " (was '"+(self.original.primitiveValue())+"')" : "")); -// spanInner.img("icon-change-edit.png", "icon"); -// spanInner.tx(" Changed"); -// } -// return; -// case Deleted: -// spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); -// spanInner.img("icon-change-remove.png", "icon"); -// spanInner.tx(" Removed"); -// return; -// default: -// x.span("color: #eeeeee").tx("n/c"); -// return; -// } -// } else { -// x.span("color: #eeeeee").tx("--"); -// } -//} + public static void renderStatusSummary(Base base, XhtmlNode x, String version, String... metadataFields) { + if (base.hasUserData(VersionComparisonAnnotation.USER_DATA_NAME)) { + VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(VersionComparisonAnnotation.USER_DATA_NAME); + switch (self.getType()) { + case Added: + XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); + spanInner.img("icon-change-add.png", "icon"); + spanInner.tx(" Added"); + return; + case Changed: + if (self.getComp().noChangeOtherThanMetadata(metadataFields)) { + x.span("color: #eeeeee").tx("n/c"); + return; + } else { + spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version+(self.getOriginal() != null ? " (was '"+(self.getOriginal())+"')" : "")); + spanInner.img("icon-change-edit.png", "icon"); + spanInner.tx(" Changed"); + } + return; + case Deleted: + spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); + spanInner.img("icon-change-remove.png", "icon"); + spanInner.tx(" Removed"); + return; + default: + x.span("color: #eeeeee").tx("n/c"); + return; + } + } else { + x.span("color: #eeeeee").tx("--"); + } + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index 7e4c92c4d..6a401a68e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -3476,7 +3476,6 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } private void generateElementInner(XhtmlNode tbl, StructureDefinition sd, ElementDefinition d, int mode, ElementDefinition value, ElementDefinition compare, ElementDefinition compareValue, boolean strikethrough) throws FHIRException, IOException { - System.out.println(d.getPath()); boolean root = !d.getPath().contains("."); boolean slicedExtension = d.hasSliceName() && (d.getPath().endsWith(".extension") || d.getPath().endsWith(".modifierExtension")); // int slicedExtensionMode = (mode == GEN_MODE_KEY) && slicedExtension ? GEN_MODE_SNAP : mode; // see ProfileUtilities.checkExtensionDoco / Task 3970 From 87a9a58771c5b059b49fd1d0e51b01d507788db6 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 17 Aug 2023 21:19:56 +1000 Subject: [PATCH 04/13] fix for testing IGs --- .../org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java | 2 +- .../org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java | 1 + .../java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java | 1 + .../main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java index f8acb936d..b57a64001 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java @@ -35,7 +35,7 @@ public class VersionComparisonAnnotation { public void changed(Base orig) { assert type == AnotationType.NoChange; type = AnotationType.Changed; - if (original != null && orig.isPrimitive() && orig.primitiveValue().length() < 120) { // arbitrary, but we don't a whack of markdown + if (orig != null && orig.isPrimitive() && orig.primitiveValue().length() < 120) { // arbitrary, but we don't a whack of markdown this.original = orig.primitiveValue(); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index 6a401a68e..e39582a4f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -4257,6 +4257,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { bindingDesc = new XhtmlNode(NodeType.Element, "div"); bindingDesc.add(new XhtmlParser().parseFragment(hostMd.processMarkdown("Binding.description", newBinding))); } else { + StringType oldBinding = compBinding != null && compBinding.hasDescription() ? PublicationHacker.fixBindingDescriptions(context.getContext(), compBinding.getDescriptionElement()) : null; bindingDesc = compareMarkdown("Binding.description", newBinding, oldBinding, mode); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java index 529c2e2cb..69c15468c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java @@ -255,6 +255,7 @@ public class RenderingContext { res.copyButton = copyButton; res.pkp = pkp; res.defaultStandardsStatus = defaultStandardsStatus; + res.changeVersion = changeVersion; res.terminologyServiceOptions = terminologyServiceOptions.copy(); return res; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java index d6ef91547..2ea74fd25 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java @@ -151,7 +151,7 @@ public class DefinitionNavigator { if (nameMap.containsKey(path)) { DefinitionNavigator master = nameMap.get(path); ElementDefinition cm = master.current(); - if (diff && cm.hasSliceName()) { + if (diff) { // slice name - jumped straight into slicing children.add(dn); } else { From c32492a66b54cc4f1585b27ad2b8362c241f70b2 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 18 Aug 2023 13:46:56 +1000 Subject: [PATCH 05/13] improve error draft error where possible --- .../hl7/fhir/r5/context/BaseWorkerContext.java | 18 +++++++++--------- .../hl7/fhir/utilities/i18n/I18nConstants.java | 9 +++++++-- .../src/main/resources/Messages.properties | 9 +++++++-- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index 151bd077b..c16d9f6b4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -1008,7 +1008,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte BundleEntryComponent r = resp.getEntry().get(i); if (r.getResource() instanceof Parameters) { - t.setResult(processValidationResult((Parameters) r.getResource())); + t.setResult(processValidationResult((Parameters) r.getResource(), vs == null ? null : vs.getUrl())); if (txCache != null) { txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT); } @@ -1108,7 +1108,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte BundleEntryComponent r = resp.getEntry().get(i); if (r.getResource() instanceof Parameters) { - t.setResult(processValidationResult((Parameters) r.getResource())); + t.setResult(processValidationResult((Parameters) r.getResource(), vsUrl)); if (txCache != null) { txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT); } @@ -1412,7 +1412,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } else { pOut = tcc.getClient().validateVS(pin); } - return processValidationResult(pOut); + return processValidationResult(pOut, vs == null ? null : vs.getUrl()); } protected void addServerValidationParameters(ValueSet vs, Parameters pin, ValidationOptions options) { @@ -1485,7 +1485,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte return cache; } - public ValidationResult processValidationResult(Parameters pOut) { + public ValidationResult processValidationResult(Parameters pOut, String vs) { boolean ok = false; String message = "No Message returned"; String display = null; @@ -1513,23 +1513,23 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED; } else if (p.getName().equals("warning-withdrawn")) { OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED); - iss.getDetails().setText(formatMessage(I18nConstants.MSG_WITHDRAWN, ((PrimitiveType) p.getValue()).asStringValue())); + iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_WITHDRAWN : I18nConstants.MSG_WITHDRAWN_SRC, ((PrimitiveType) p.getValue()).asStringValue(), vs)); issues.add(iss); } else if (p.getName().equals("warning-deprecated")) { OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED); - iss.getDetails().setText(formatMessage(I18nConstants.MSG_DEPRECATED, ((PrimitiveType) p.getValue()).asStringValue())); + iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DEPRECATED : I18nConstants.MSG_DEPRECATED_SRC, ((PrimitiveType) p.getValue()).asStringValue(), vs)); issues.add(iss); } else if (p.getName().equals("warning-retired")) { OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED); - iss.getDetails().setText(formatMessage(I18nConstants.MSG_RETIRED, ((PrimitiveType) p.getValue()).asStringValue())); + iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_RETIRED : I18nConstants.MSG_RETIRED_SRC, ((PrimitiveType) p.getValue()).asStringValue(), vs)); issues.add(iss); } else if (p.getName().equals("warning-experimental")) { OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE); - iss.getDetails().setText(formatMessage(I18nConstants.MSG_EXPERIMENTAL, ((PrimitiveType) p.getValue()).asStringValue())); + iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_EXPERIMENTAL : I18nConstants.MSG_EXPERIMENTAL_SRC, ((PrimitiveType) p.getValue()).asStringValue(), vs)); issues.add(iss); } else if (p.getName().equals("warning-draft")) { OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE); - iss.getDetails().setText(formatMessage(I18nConstants.MSG_DRAFT, ((PrimitiveType) p.getValue()).asStringValue())); + iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DRAFT : I18nConstants.MSG_DRAFT_SRC, ((PrimitiveType) p.getValue()).asStringValue(), vs)); issues.add(iss); } else if (p.getName().equals("cause")) { try { 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 4014de990..a14be8625 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 @@ -954,10 +954,15 @@ public class I18nConstants { public static final String MSG_DEPRECATED = "MSG_DEPRECATED"; public static final String MSG_WITHDRAWN = "MSG_WITHDRAWN"; public static final String MSG_RETIRED = "MSG_RETIRED"; - public static final String INACTIVE_CODE_WARNING = "INACTIVE_CODE_WARNING"; - public static final String SD_EXTENSION_URL_MISSING = "SD_EXTENSION_URL_MISSING"; public static final String MSG_EXPERIMENTAL = "MSG_EXPERIMENTAL"; public static final String MSG_DRAFT = "MSG_DRAFT"; + public static final String MSG_DEPRECATED_SRC = "MSG_DEPRECATED_SRC"; + public static final String MSG_WITHDRAWN_SRC = "MSG_WITHDRAWN_SRC"; + public static final String MSG_RETIRED_SRC = "MSG_RETIRED_SRC"; + public static final String MSG_DRAFT_SRC = "MSG_DRAFT_SRC"; + public static final String MSG_EXPERIMENTAL_SRC = "MSG_EXPERIMENTAL_SRC"; + public static final String INACTIVE_CODE_WARNING = "INACTIVE_CODE_WARNING"; + public static final String SD_EXTENSION_URL_MISSING = "SD_EXTENSION_URL_MISSING"; public static final String MSG_DEPENDS_ON_DEPRECATED = "MSG_DEPENDS_ON_DEPRECATED"; public static final String MSG_DEPENDS_ON_WITHDRAWN = "MSG_DEPENDS_ON_WITHDRAWN"; public static final String MSG_DEPENDS_ON_RETIRED = "MSG_DEPENDS_ON_RETIRED"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 32de966b0..cbd93e2fc 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -1009,11 +1009,16 @@ FHIRPATH_OFTYPE_IMPOSSIBLE = The type specified in ofType is {1} which is not a ED_SEARCH_EXPRESSION_ERROR = Error in search expression ''{0}'': {1} SD_EXTENSION_URL_MISMATCH = The fixed value for the extension URL is {1} which doesn''t match the canonical URL {0} SD_EXTENSION_URL_MISSING = The value of Extension.url is not fixed to the extension URL {0} -MSG_DEPRECATED = Reference to deprecated item {0} +MSG_DEPRECATED = Reference to deprecated item {0} MSG_WITHDRAWN = Reference to withdrawn item {0} -MSG_RETIRED = Reference to retired item {0} +MSG_RETIRED = Reference to retired item {0} MSG_EXPERIMENTAL = Reference to experimental item {0} MSG_DRAFT = Reference to draft item {0} +MSG_DEPRECATED_SRC = Reference to deprecated item {0} from {1} +MSG_WITHDRAWN_SRC = Reference to withdrawn item {0} from {1} +MSG_RETIRED_SRC = Reference to retired item {0} from {1} +MSG_EXPERIMENTAL_SRC = Reference to experimental item {0} from {1} +MSG_DRAFT_SRC = Reference to draft item {0} from {1} INACTIVE_CODE_WARNING = The code ''{0}'' is valid but is not active SD_ED_TYPE_PROFILE_WRONG_TYPE_one = The type {0} is not in the list of allowed type {1} in the profile {2} SD_ED_TYPE_PROFILE_WRONG_TYPE_other = The type {0} is not in the list of allowed types {1} in the profile {2} From bb2e87c250a16ae0af56fb31b88144f299722ea5 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 18 Aug 2023 13:47:16 +1000 Subject: [PATCH 06/13] automatically process markdown in code system concept definitions --- .../fhir/r5/renderers/CodeSystemRenderer.java | 11 ++++++++++- .../r5/terminologies/CodeSystemUtilities.java | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java index 727707172..f4e40ae6a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java @@ -37,6 +37,8 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class CodeSystemRenderer extends TerminologyRenderer { + private Boolean doMarkdown = null; + public CodeSystemRenderer(RenderingContext context) { super(context); } @@ -616,7 +618,14 @@ public class CodeSystemRenderer extends TerminologyRenderer { } private boolean hasMarkdownInDefinitions(CodeSystem cs) { - return ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown"); + if (doMarkdown == null) { + if (cs.hasExtension("http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown")) { + doMarkdown = ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown"); + } else { + doMarkdown = CodeSystemUtilities.hasMarkdownInDefinitions(cs, context.getMarkdown()); + } + } + return doMarkdown; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java index 098fd9f5d..5c3ce0bce 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java @@ -66,6 +66,7 @@ import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.UriType; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.StandardsStatus; import org.hl7.fhir.utilities.Utilities; @@ -888,5 +889,21 @@ public class CodeSystemUtilities { ConceptPropertyComponent cp = getProperty(def, property); return cp == null ? null : cp.getValue(); } + + public static boolean hasMarkdownInDefinitions(CodeSystem cs, MarkDownProcessor md) { + return hasMarkdownInDefinitions(cs.getConcept(), md); + } + + private static boolean hasMarkdownInDefinitions(List concepts, MarkDownProcessor md) { + for (ConceptDefinitionComponent c : concepts) { + if (c.hasDefinition() && md.isProbablyMarkdown(c.getDefinition(), true)) { + return true; + } + if (c.hasConcept() && hasMarkdownInDefinitions(c.getConcept(), md)) { + return true; + } + } + return false; + } } From d571f48376c10eca47f19d91271797f3dfde19ea Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 18 Aug 2023 13:47:34 +1000 Subject: [PATCH 07/13] fix bug in compare utilities around json arrays with multiple optional elements --- .../fhir/r5/test/utils/CompareUtilities.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java index cd30eae1a..84da71c09 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java @@ -350,12 +350,15 @@ public class CompareUtilities extends BaseTestingUtilities { JsonArray expectedArray = (JsonArray) expectedJsonElement; int expectedMin = countExpectedMin(expectedArray); - if (actualArray.size() > expectedArray.size() || actualArray.size() < expectedMin) - return createNotEqualMessage("array item count differs at " + path, Integer.toString(expectedArray.size()), Integer.toString(actualArray.size())); + int as = actualArray.size(); + int es = expectedArray.size(); + int oc = optionalCount(expectedArray); + if (as > es || as < expectedMin) + return createNotEqualMessage("array item count differs at " + path, Integer.toString(es), Integer.toString(as)); int c = 0; - for (int i = 0; i < expectedArray.size(); i++) { - if (c >= actualArray.size()) { - if (i == expectedArray.size() - 1 && isOptional(expectedArray.get(i))) { + for (int i = 0; i < es; i++) { + if (c >= as) { + if (i >= es - oc && isOptional(expectedArray.get(i))) { return null; // this is OK } else { return "One or more array items did not match at "+path+" starting at index "+i; @@ -375,7 +378,20 @@ public class CompareUtilities extends BaseTestingUtilities { return null; } - private boolean isOptional(JsonElement e) { + private int optionalCount(JsonArray arr) { + int c = 0; + for (JsonElement e : arr) { + if (e.isJsonObject()) { + JsonObject j = e.asJsonObject(); + if (j.has("$optional$") && j.asBoolean("$optional$")) { + c++; + } + } + } + return c; + } + +private boolean isOptional(JsonElement e) { return e.isJsonObject() && e.asJsonObject().has("$optional$"); } From a87e4728a40b9d20b83928f9983dcb57f3c1bdbe Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 18 Aug 2023 13:47:53 +1000 Subject: [PATCH 08/13] fix bug in structure definition creating links to core spec --- .../fhir/r5/renderers/StructureDefinitionRenderer.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index e39582a4f..76f735a44 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -338,6 +338,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { public StructureDefinitionRenderer(RenderingContext context) { super(context); hostMd = new InternalMarkdownProcessor(); + corePath = context.getContext().getSpecUrl(); } public StructureDefinitionRenderer(RenderingContext context, ResourceContext rcontext) { @@ -3353,8 +3354,15 @@ public class StructureDefinitionRenderer extends ResourceRenderer { if (compare == null || mode == GEN_MODE_DIFF) { if (md.hasValue()) { String xhtml = hostMd.processMarkdown(location, md); + if (Utilities.noString(xhtml)) { + return null; + } XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); - renderStatusDiv(md, x).add(new XhtmlParser().parseFragment(xhtml)); + try { + renderStatusDiv(md, x).add(new XhtmlParser().parseFragment(xhtml)); + } catch (Exception e) { + x.span("color: maroon").tx(e.getLocalizedMessage()); + } return x; } else { return null; From fa54f7748e3e26edb40be47427b41cc1b561cf8d Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 18 Aug 2023 13:48:08 +1000 Subject: [PATCH 09/13] fix java exception --- .../main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java index 291a24c3a..37791e26e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java @@ -481,7 +481,7 @@ public class ValueSetRenderer extends TerminologyRenderer { } private void expRef(XhtmlNode x, String u, String v, Resource source) { - String t = u.substring(0, u.indexOf("|")); + String t = u.contains("|") ? u.substring(0, u.indexOf("|")) : u; u = u.substring(u.indexOf("|")+1); // TODO Auto-generated method stub if (u.equals("http://snomed.info/sct")) { From 5078a950169f96f088f99b89c20491c83fa97d3a Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 18 Aug 2023 13:48:24 +1000 Subject: [PATCH 10/13] update CPT model per changes to doco --- .../hl7/fhir/convertors/misc/CPTImporter.java | 78 ++++++++++++++++--- 1 file changed, 67 insertions(+), 11 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CPTImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CPTImporter.java index 7ec1db586..a8a90c372 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CPTImporter.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CPTImporter.java @@ -11,6 +11,8 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Date; +import java.util.List; +import java.util.ArrayList; import java.util.Scanner; import org.hl7.fhir.exceptions.FHIRException; @@ -56,19 +58,23 @@ public class CPTImporter { cs.setCopyright("CPT © Copyright 2019 American Medical Association. All rights reserved. AMA and CPT are registered trademarks of the American Medical Association."); cs.addProperty().setCode("modifier").setDescription("Whether code is a modifier code").setType(PropertyType.BOOLEAN); cs.addProperty().setCode("modified").setDescription("Whether code has been modified (all base codes are not modified)").setType(PropertyType.BOOLEAN); + cs.addProperty().setCode("orthopox").setDescription("Whether code is one of the Pathology and Laboratory and Immunization Code(s) for Orthopoxvirus").setType(PropertyType.BOOLEAN); + cs.addProperty().setCode("telemedicine").setDescription("Whether code is appropriate for use with telemedicine (and the telemedicine modifier)").setType(PropertyType.BOOLEAN); cs.addProperty().setCode("kind").setDescription("Kind of Code (see metadata)").setType(PropertyType.CODE); defineMetadata(cs); - System.out.println(readCodes(cs, Utilities.path(src, "LONGULT.txt"), false, null, null)); - System.out.println(readCodes(cs, Utilities.path(src, "LONGUT.txt"), false, "upper", null)); - System.out.println(readCodes(cs, Utilities.path(src, "MEDU.txt"), false, "med", null)); - System.out.println(readCodes(cs, Utilities.path(src, "SHORTU.txt"), false, "short", null)); - System.out.println(readCodes(cs, Utilities.path(src, "ConsumerDescriptor.txt"), true, "consumer", null)); - System.out.println(readCodes(cs, Utilities.path(src, "ClinicianDescriptor.txt"), true, "clinician", null)); - System.out.println(readCodes(cs, Utilities.path(src, "OrthopoxvirusCodes.txt"), false, null, "orthopod")); + System.out.println("LONGULT: "+readCodes(cs, Utilities.path(src, "LONGULT.txt"), false, null, null, null)); + System.out.println("LONGUT: "+readCodes(cs, Utilities.path(src, "LONGUT.txt"), false, "upper", null, null)); + System.out.println("MEDU: "+readCodes(cs, Utilities.path(src, "MEDU.txt"), false, "med", null, null)); + System.out.println("SHORTU: "+readCodes(cs, Utilities.path(src, "SHORTU.txt"), false, "short", null, null)); + System.out.println("ConsumerDescriptor: "+readCodes(cs, Utilities.path(src, "ConsumerDescriptor.txt"), true, "consumer", null, null)); + System.out.println("ClinicianDescriptor: "+readCodes(cs, Utilities.path(src, "ClinicianDescriptor.txt"), true, "clinician", null, null)); + System.out.println("OrthopoxvirusCodes: "+readCodes(cs, Utilities.path(src, "OrthopoxvirusCodes.txt"), false, null, null, "orthopox")); - System.out.println(processModifiers(cs, Utilities.path(src, "modifiers.csv"))); + System.out.println("modifiers: "+processModifiers(cs, Utilities.path(src, "modifiers.csv"))); + System.out.println("appendix P: "+processAppendixP(cs)); + System.out.println("-------------------"); System.out.println(cs.getConcept().size()); @@ -87,11 +93,59 @@ public class CPTImporter { produceDB(Utilities.changeFileExt(dst, ".db"), cs); cs.setContent(CodeSystemContentMode.FRAGMENT); - cs.getConcept().removeIf(cc -> !Utilities.existsInList(cc.getCode(), "metadata-kinds", "metadata-designations", "99202", "99203", "0001A", "25", "P1", "1P", "F1")); + cs.getConcept().removeIf(cc -> !Utilities.existsInList(cc.getCode(), "metadata-kinds", "metadata-designations", "99202", "99203", "0001A", "99252", "25", "P1", "1P", "F1", "95")); new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.changeFileExt(dst, "-fragment.json")), cs); produceDB(Utilities.changeFileExt(dst, "-fragment.db"), cs); } + private String processAppendixP(CodeSystem cs) { + List tcodes = new ArrayList<>(); + tcodes.add("90785"); + tcodes.add("90791"); + tcodes.add("90792"); + tcodes.add("90832"); + tcodes.add("90833"); + tcodes.add("90834"); + tcodes.add("90836"); + tcodes.add("90837"); + tcodes.add("90838"); + tcodes.add("90839"); + tcodes.add("90840"); + tcodes.add("90845"); + tcodes.add("90846"); + tcodes.add("90847"); + tcodes.add("92507"); + tcodes.add("92508"); + tcodes.add("92521"); + tcodes.add("92522"); + tcodes.add("92523"); + tcodes.add("92524"); + tcodes.add("96040"); + tcodes.add("96110"); + tcodes.add("96116"); + tcodes.add("96160"); + tcodes.add("96161"); + tcodes.add("97802"); + tcodes.add("97803"); + tcodes.add("97804"); + tcodes.add("99406"); + tcodes.add("99407"); + tcodes.add("99408"); + tcodes.add("99409"); + tcodes.add("99497"); + tcodes.add("99498"); + + for (String c : tcodes) { + ConceptDefinitionComponent cc = CodeSystemUtilities.findCode(cs.getConcept(), c); + if (cc == null) { + throw new Error("unable to find tcode "+c); + } + cc.addProperty().setCode("telemedicine").setValue(new BooleanType(true)); + } + return String.valueOf(tcodes.size()); + } + + private void produceDB(String path, CodeSystem cs) throws ClassNotFoundException, SQLException { Connection con = connect(path); @@ -197,7 +251,6 @@ public class CPTImporter { mm(pc.addConcept()).setCode("physical-status").setDisplay("Anesthesia Physical Status Modifiers"); mm(pc.addConcept()).setCode("general").setDisplay("A general modifier"); mm(pc.addConcept()).setCode("hcpcs").setDisplay("Level II (HCPCS/National) Modifiers"); - mm(pc.addConcept()).setCode("orthopox").setDisplay(""); mm(pc.addConcept()).setCode("metadata").setDisplay("A kind of code or designation"); ConceptDefinitionComponent dc = mm(cs.addConcept().setCode("metadata-designations")); @@ -251,7 +304,7 @@ public class CPTImporter { return res; } - private int readCodes(CodeSystem cs, String path, boolean hasConceptId, String use, String type) throws IOException { + private int readCodes(CodeSystem cs, String path, boolean hasConceptId, String use, String type, String boolProp) throws IOException { int res = 0; FileInputStream inputStream = null; Scanner sc = null; @@ -286,6 +339,9 @@ public class CPTImporter { } else if (type != null) { cc.addProperty().setCode("kind").setValue(new CodeType(type)); } + if (boolProp != null) { + cc.addProperty().setCode(boolProp).setValue(new BooleanType(true)); + } if (use == null) { if (cc.hasDisplay()) { System.err.println("?"); From 6f0d54a8dc4f0d43d6458b3dfc7919ca50ac06f9 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 19 Aug 2023 09:03:59 +1000 Subject: [PATCH 11/13] terminology service bug fixes --- .../fhir/r5/context/BaseWorkerContext.java | 15 +++- .../r5/context/CanonicalResourceManager.java | 2 +- .../expansion/ValueSetExpander.java | 42 ++++++--- .../TerminologyOperationContext.java | 86 +++++++++++++++++++ .../utilities/ValueSetProcessBase.java | 13 ++- .../validation/ValueSetValidator.java | 50 +++++++++-- .../CommaSeparatedStringBuilder.java | 8 ++ .../fhir/utilities/i18n/I18nConstants.java | 3 + .../src/main/resources/Messages.properties | 9 +- .../src/main/resources/Messages_es.properties | 3 + .../ExternalTerminologyServiceTests.java | 4 +- 11 files changed, 206 insertions(+), 29 deletions(-) create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyOperationContext.java diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index c16d9f6b4..5aee1c730 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -113,6 +113,8 @@ import org.hl7.fhir.r5.renderers.OperationOutcomeRenderer; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander; import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; +import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext; +import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException; import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.validation.VSCheckerException; import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator; @@ -1185,12 +1187,17 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } catch (VSCheckerException e) { if (e.isWarning()) { localWarning = e.getMessage(); - } else { + } else { localError = e.getMessage(); } if (e.getIssues() != null) { issues.addAll(e.getIssues()); } + } catch (TerminologyServiceProtectionException e) { + OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR, e.getType()); + iss.getDetails().setText(e.getMessage()); + issues.add(iss); + return new ValidationResult(IssueSeverity.ERROR, e.getMessage(), e.getError(), issues); } catch (Exception e) { // e.printStackTrace(); localError = e.getMessage(); @@ -1246,15 +1253,15 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } protected ValueSetExpander constructValueSetExpanderSimple() { - return new ValueSetExpander(this); + return new ValueSetExpander(this, new TerminologyOperationContext(this)); } protected ValueSetValidator constructValueSetCheckerSimple( ValidationOptions options, ValueSet vs, ValidationContextCarrier ctxt) { - return new ValueSetValidator(options, vs, this, ctxt, expParameters, tcc.getTxcaps()); + return new ValueSetValidator(this, new TerminologyOperationContext(this), options, vs, ctxt, expParameters, tcc.getTxcaps()); } protected ValueSetValidator constructValueSetCheckerSimple( ValidationOptions options, ValueSet vs) { - return new ValueSetValidator(options, vs, this, expParameters, tcc.getTxcaps()); + return new ValueSetValidator(this, new TerminologyOperationContext(this), options, vs, expParameters, tcc.getTxcaps()); } protected Parameters constructParameters(ValueSet vs, boolean hierarchical) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java index e5b84f0a9..469859583 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java @@ -594,7 +594,7 @@ public class CanonicalResourceManager { if (list != null) { for (CanonicalResourceManager.CachedCanonicalResource t : list) { possibleMatches = true; - if (pvlist == null || pvlist.contains(t.getPackageInfo().getVID())) { + if (pvlist == null || t.getPackageInfo() == null || pvlist.contains(t.getPackageInfo().getVID())) { res.add(t.getResource()); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpander.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpander.java index e17618eaa..763fa7c3b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpander.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpander.java @@ -114,9 +114,12 @@ import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; import org.hl7.fhir.r5.terminologies.ValueSetUtilities; import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider; import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension; +import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext; +import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException; import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase; import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.i18n.I18nConstants; @@ -127,20 +130,18 @@ public class ValueSetExpander extends ValueSetProcessBase { private ValueSet focus; private List allErrors = new ArrayList<>(); - private List requiredSupplements = new ArrayList<>(); private int maxExpansionSize = 1000; private WorkingContext dwc = new WorkingContext(); private boolean checkCodesWhenExpanding; private boolean includeAbstract = true; - public ValueSetExpander(IWorkerContext context) { - this.context = context; + public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext) { + super(context, opContext); } - public ValueSetExpander(IWorkerContext context, List allErrors) { - super(); - this.context = context; + public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext, List allErrors) { + super(context, opContext); this.allErrors = allErrors; } @@ -151,7 +152,8 @@ public class ValueSetExpander extends ValueSetProcessBase { private ValueSetExpansionContainsComponent addCode(WorkingContext wc, String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List designations, Parameters expParams, boolean isAbstract, boolean inactive, List filters, boolean noInactive, boolean deprecated, List vsProp, List csProps, List expProps, List csExtList, List vsExtList, ValueSetExpansionComponent exp) throws ETooCostly { - + opContext.deadCheck(); + if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp)) return null; if (noInactive && inactive) { @@ -311,6 +313,7 @@ public class ValueSetExpander extends ValueSetProcessBase { } private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, boolean noInactive, List vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { + opContext.deadCheck(); focus.checkNoModifiers("Expansion.contains", "expanding"); ValueSetExpansionContainsComponent np = null; for (String code : getCodesForConcept(focus, expParams)) { @@ -360,6 +363,7 @@ public class ValueSetExpander extends ValueSetProcessBase { private void addCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List vsProps, List otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { + opContext.deadCheck(); def.checkNoModifiers("Code in Code System", "expanding"); if (exclusion != null) { if (exclusion.getCode().equals(def.getCode())) @@ -432,6 +436,7 @@ public class ValueSetExpander extends ValueSetProcessBase { } private void excludeCodes(WorkingContext wc, ConceptSetComponent exc, List params, String ctxt) throws FHIRException { + opContext.deadCheck(); exc.checkNoModifiers("Compose.exclude", "expanding"); if (exc.hasSystem() && exc.getConcept().size() == 0 && exc.getFilter().size() == 0) { wc.getExcludeSystems().add(exc.getSystem()); @@ -460,6 +465,7 @@ public class ValueSetExpander extends ValueSetProcessBase { } private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand, List params) { + opContext.deadCheck(); for (ValueSetExpansionContainsComponent c : expand.getContains()) { excludeCode(wc, c.getSystem(), c.getCode()); } @@ -475,8 +481,11 @@ public class ValueSetExpander extends ValueSetProcessBase { } public ValueSetExpansionOutcome expand(ValueSet source, Parameters expParams) { + allErrors.clear(); try { + opContext.seeContext(source.getVersionedUrl()); + return expandInternal(source, expParams); } catch (NoTerminologyServiceException e) { // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set @@ -486,6 +495,12 @@ public class ValueSetExpander extends ValueSetProcessBase { // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set // that might fail too, but it might not, later. return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors); + } catch (TerminologyServiceProtectionException e) { + if (opContext.isOriginal()) { + return new ValueSetExpansionOutcome(e.getMessage(), e.getError(), allErrors); + } else { + throw e; + } } catch (ETooCostly e) { return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors); } catch (Exception e) { @@ -574,8 +589,8 @@ public class ValueSetExpander extends ValueSetProcessBase { if (dwc.getTotal() >= 0) { focus.getExpansion().setTotal(dwc.getTotal()); } - if (!requiredSupplements.isEmpty()) { - return new ValueSetExpansionOutcome("Required supplements not found: "+requiredSupplements.toString(), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors); + if (!requiredSupplements.isEmpty()) { + return new ValueSetExpansionOutcome(context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors); } if (!expParams.hasParameter("includeDefinition") || !expParams.getParameterBool("includeDefinition")) { focus.setCompose(null); @@ -656,7 +671,7 @@ public class ValueSetExpander extends ValueSetProcessBase { expParams = expParams.copy(); expParams.addParameter("activeOnly", true); } - ValueSetExpansionOutcome vso = new ValueSetExpander(context, allErrors).expand(vs, expParams); + ValueSetExpansionOutcome vso = new ValueSetExpander(context, opContext.copy(), allErrors).expand(vs, expParams); if (vso.getError() != null) { addErrors(vso.getAllErrors()); throw fail("Unable to expand imported value set "+vs.getUrl()+": " + vso.getError()); @@ -697,6 +712,7 @@ public class ValueSetExpander extends ValueSetProcessBase { } public void copyExpansion(WorkingContext wc,List list) { + opContext.deadCheck(); for (ValueSetExpansionContainsComponent cc : list) { ValueSetExpansionContainsComponent n = new ValueSet.ValueSetExpansionContainsComponent(); n.setSystem(cc.getSystem()); @@ -725,6 +741,7 @@ public class ValueSetExpander extends ValueSetProcessBase { } private void copyImportContains(List list, ValueSetExpansionContainsComponent parent, Parameters expParams, List filter, boolean noInactive, List vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { + opContext.deadCheck(); for (ValueSetExpansionContainsComponent c : list) { c.checkNoModifiers("Imported Expansion in Code System", "expanding"); ValueSetExpansionContainsComponent np = addCode(dwc, c.getSystem(), c.getCode(), c.getDisplay(), vsSrc.getLanguage(), parent, null, expParams, c.getAbstract(), c.getInactive(), @@ -734,6 +751,7 @@ public class ValueSetExpander extends ValueSetProcessBase { } private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension { + opContext.deadCheck(); inc.checkNoModifiers("Compose.include", "expanding"); List imports = new ArrayList(); for (CanonicalType imp : inc.getValueSet()) { @@ -764,6 +782,7 @@ public class ValueSetExpander extends ValueSetProcessBase { } private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List imports, Parameters expParams, List extensions, boolean noInactive, List vsProps) throws FHIRException, CodeSystemProviderExtension, ETooCostly { + opContext.deadCheck(); CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem()); if (csp != null) { csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps); @@ -800,6 +819,7 @@ public class ValueSetExpander extends ValueSetProcessBase { public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List imports, CodeSystem cs, boolean noInactive, Resource vsSrc) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException, ETooCostly { + opContext.deadCheck(); if (cs == null) { if (context.isNoTerminologyServer()) throw failTSE("Unable to find code system " + inc.getSystem().toString()); @@ -884,6 +904,7 @@ public class ValueSetExpander extends ValueSetProcessBase { private void processFilter(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List imports, CodeSystem cs, boolean noInactive, ConceptSetFilterComponent fc, WorkingContext wc, List filters) throws ETooCostly { + opContext.deadCheck(); if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISA) { // special: all codes in the target code system under the value ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); @@ -919,6 +940,7 @@ public class ValueSetExpander extends ValueSetProcessBase { if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) { if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) { for (String code : getCodesForConcept(def, expParams)) { + opContext.deadCheck(); ValueSetExpansionContainsComponent t = addCode(wc, inc.getSystem(), code, def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), imports, noInactive, false, exp.getProperty(), makeCSProps(def.getDefinition(), def.getProperty()), null, def.getExtension(), null, exp); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyOperationContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyOperationContext.java new file mode 100644 index 000000000..de31e7465 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyOperationContext.java @@ -0,0 +1,86 @@ +package org.hl7.fhir.r5.terminologies.utilities; + +import java.util.List; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.TerminologyServiceException; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.OperationOutcome.IssueType; +import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException; +import org.hl7.fhir.utilities.i18n.I18nConstants; + +import java.util.ArrayList; + +public class TerminologyOperationContext { + + public class TerminologyServiceProtectionException extends FHIRException { + + private TerminologyServiceErrorClass error; + private IssueType type; + + protected TerminologyServiceProtectionException(String message, TerminologyServiceErrorClass error, IssueType type) { + super(message); + this.error = error; + this.type = type; + } + + public TerminologyServiceErrorClass getError() { + return error; + } + + public IssueType getType() { + return type; + } + + } + + public static boolean debugging = java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments().toString().indexOf("-agentlib:jdwp") > 0; + private static final int EXPANSION_DEAD_TIME_SECS = 60; + private long deadTime; + private List contexts = new ArrayList<>(); + private IWorkerContext worker; + private boolean original; + + public TerminologyOperationContext(IWorkerContext worker) { + super(); + this.worker = worker; + this.original = true; + + if (EXPANSION_DEAD_TIME_SECS == 0 || debugging) { + deadTime = 0; + } else { + deadTime = System.currentTimeMillis() + (EXPANSION_DEAD_TIME_SECS * 1000); + } + } + + private TerminologyOperationContext() { + super(); + } + + public TerminologyOperationContext copy() { + TerminologyOperationContext ret = new TerminologyOperationContext(); + ret.worker = worker; + ret.contexts.addAll(contexts); + ret.deadTime = deadTime; + return ret; + } + + public void deadCheck() { + if (deadTime != 0 && System.currentTimeMillis() > deadTime) { + throw new TerminologyServiceProtectionException(worker.formatMessage(I18nConstants.VALUESET_TOO_COSTLY_TIME, contexts.get(0), EXPANSION_DEAD_TIME_SECS), TerminologyServiceErrorClass.TOO_COSTLY, IssueType.TOOCOSTLY); + } + } + + public void seeContext(String context) { + if (contexts.contains(context)) { + throw new TerminologyServiceProtectionException(worker.formatMessage(I18nConstants.VALUESET_CIRCULAR_REFERENCE, context, contexts.toString()), TerminologyServiceErrorClass.BUSINESS_RULE, IssueType.BUSINESSRULE); + } + contexts.add(context); + } + + public boolean isOriginal() { + return original; + } + + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java index 551711e65..e1e8fbd0c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java @@ -25,7 +25,14 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; public class ValueSetProcessBase { protected IWorkerContext context; - + protected TerminologyOperationContext opContext; + protected List requiredSupplements = new ArrayList<>(); + + protected ValueSetProcessBase(IWorkerContext context, TerminologyOperationContext opContext) { + super(); + this.context = context; + this.opContext = opContext; + } public static class AlternateCodesProcessingRules { private boolean all; private List uses = new ArrayList<>(); @@ -106,7 +113,9 @@ public class ValueSetProcessBase { break; } result.setCode(type); - result.addLocation(location); + if (location != null) { + result.addLocation(location); + } result.getDetails().setText(message); ArrayList list = new ArrayList<>(); list.add(result); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java index 7d2779098..55bf7eebb 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java @@ -47,6 +47,7 @@ import org.hl7.fhir.exceptions.NoTerminologyServiceException; import org.hl7.fhir.r5.context.ContextUtilities; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; +import org.hl7.fhir.r5.extensions.ExtensionConstants; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; @@ -79,6 +80,7 @@ import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider; import org.hl7.fhir.r5.terminologies.providers.SpecialCodeSystem; import org.hl7.fhir.r5.terminologies.providers.URICodeSystem; +import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext; import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase; import org.hl7.fhir.r5.utils.ToolingExtensions; @@ -106,18 +108,18 @@ public class ValueSetValidator extends ValueSetProcessBase { private Set unknownSystems; private boolean throwToServer; - public ValueSetValidator(ValidationOptions options, ValueSet source, IWorkerContext context, Parameters expansionProfile, TerminologyCapabilities txCaps) { + public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, Parameters expansionProfile, TerminologyCapabilities txCaps) { + super(context, opContext); this.valueset = source; - this.context = context; this.options = options; this.expansionProfile = expansionProfile; this.txCaps = txCaps; analyseValueSet(); } - public ValueSetValidator(ValidationOptions options, ValueSet source, IWorkerContext context, ValidationContextCarrier ctxt, Parameters expansionProfile, TerminologyCapabilities txCaps) { + public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, ValidationContextCarrier ctxt, Parameters expansionProfile, TerminologyCapabilities txCaps) { + super(context, opContext); this.valueset = source; - this.context = context; this.options = options.copy(); this.options.setEnglishOk(true); this.localContext = ctxt; @@ -143,6 +145,13 @@ public class ValueSetValidator extends ValueSetProcessBase { } private void analyseValueSet() { + if (valueset != null) { + opContext.seeContext(valueset.getVersionedUrl()); + for (Extension s : valueset.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) { + requiredSupplements.add(s.getValue().primitiveValue()); + } + } + altCodeParams.seeParameters(expansionProfile); altCodeParams.seeValueSet(valueset); if (localContext != null) { @@ -158,6 +167,7 @@ public class ValueSetValidator extends ValueSetProcessBase { } private void analyseComponent(ConceptSetComponent i) { + opContext.deadCheck(); if (i.getSystemElement().hasExtension(ToolingExtensions.EXT_VALUESET_SYSTEM)) { String ref = i.getSystemElement().getExtensionString(ToolingExtensions.EXT_VALUESET_SYSTEM); if (ref.startsWith("#")) { @@ -179,6 +189,8 @@ public class ValueSetValidator extends ValueSetProcessBase { } public ValidationResult validateCode(String path, CodeableConcept code) throws FHIRException { + opContext.deadCheck(); + // first, we validate the codings themselves ValidationProcessInfo info = new ValidationProcessInfo(); @@ -250,7 +262,9 @@ public class ValueSetValidator extends ValueSetProcessBase { info.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, path, msg)); } } - if (info.hasErrors()) { + if (!checkRequiredSupplements(info)) { + return new ValidationResult(IssueSeverity.ERROR, info.getIssues().get(info.getIssues().size()-1).getDetails().getText(), info.getIssues()); + } else if (info.hasErrors()) { ValidationResult res = new ValidationResult(IssueSeverity.ERROR, info.summary(), info.getIssues()); if (foundCoding != null) { ConceptDefinitionComponent cd = new ConceptDefinitionComponent(foundCoding.getCode()); @@ -279,6 +293,13 @@ public class ValueSetValidator extends ValueSetProcessBase { } } + private boolean checkRequiredSupplements(ValidationProcessInfo info) { + if (!requiredSupplements.isEmpty()) { + info.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, null, context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)))); + } + return requiredSupplements.isEmpty(); + } + private boolean valueSetDependsOn(String system, String version) { for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { if (system.equals(inc.getSystem()) && (version == null || inc.getVersion() == null || version.equals(inc.getVersion()))) { @@ -315,7 +336,7 @@ public class ValueSetValidator extends ValueSetProcessBase { return t; } } - CodeSystem cs = context.fetchCodeSystem(system, version); + CodeSystem cs = context.fetchSupplementedCodeSystem(system, version); if (cs == null) { cs = findSpecialCodeSystem(system, version); } @@ -342,6 +363,7 @@ public class ValueSetValidator extends ValueSetProcessBase { } public ValidationResult validateCode(String path, Coding code) throws FHIRException { + opContext.deadCheck(); String warningMessage = null; // first, we validate the concept itself @@ -460,6 +482,9 @@ public class ValueSetValidator extends ValueSetProcessBase { inInclude = checkInclude(code, vi); } String wv = vi.getVersion(system, code.getVersion()); + if (!checkRequiredSupplements(info)) { + return new ValidationResult(IssueSeverity.ERROR, issues.get(issues.size()-1).getDetails().getText(), issues); + } // then, if we have a value set, we check it's in the value set @@ -597,6 +622,7 @@ public class ValueSetValidator extends ValueSetProcessBase { private ValidationResult findCodeInExpansion(Coding code, List contains) { for (ValueSetExpansionContainsComponent containsComponent: contains) { + opContext.deadCheck(); if (containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) { ConceptDefinitionComponent ccd = new ConceptDefinitionComponent(); ccd.setCode(containsComponent.getCode()); @@ -623,6 +649,7 @@ public class ValueSetValidator extends ValueSetProcessBase { private boolean checkExpansion(Coding code, List contains, VersionInfo vi) { for (ValueSetExpansionContainsComponent containsComponent: contains) { + opContext.deadCheck(); if (containsComponent.hasSystem() && containsComponent.hasCode() && containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) { vi.setExpansionVersion(containsComponent.getVersion()); return true; @@ -667,6 +694,7 @@ public class ValueSetValidator extends ValueSetProcessBase { } for (ConceptDefinitionDesignationComponent ds : cc.getDesignation()) { + opContext.deadCheck(); if (isOkLanguage(ds.getLanguage())) { b.append("'"+ds.getValue()+"'"); if (code.getDisplay().equalsIgnoreCase(ds.getValue())) { @@ -688,6 +716,7 @@ public class ValueSetValidator extends ValueSetProcessBase { } } for (ConceptReferenceDesignationComponent ds : vs.getCc().getDesignation()) { + opContext.deadCheck(); if (isOkLanguage(ds.getLanguage())) { b.append("'"+ds.getValue()+"'"); if (code.getDisplay().equalsIgnoreCase(ds.getValue())) { @@ -732,6 +761,7 @@ public class ValueSetValidator extends ValueSetProcessBase { return null; // if it has an expansion for (ValueSetExpansionContainsComponent exp : valueset.getExpansion().getContains()) { + opContext.deadCheck(); if (system.equals(exp.getSystem()) && code.equals(exp.getCode())) { ConceptReferenceComponent cc = new ConceptReferenceComponent(); cc.setDisplay(exp.getDisplay()); @@ -810,6 +840,7 @@ public class ValueSetValidator extends ValueSetProcessBase { } private ConceptDefinitionComponent findCodeInConcept(ConceptDefinitionComponent concept, String code, AlternateCodesProcessingRules altCodeRules) { + opContext.deadCheck(); if (code.equals(concept.getCode())) { return concept; } @@ -879,6 +910,7 @@ public class ValueSetValidator extends ValueSetProcessBase { int i = 0; for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) { + opContext.deadCheck(); if (vsi.hasValueSet()) { for (CanonicalType u : vsi.getValueSet()) { if (!checkForCodeInValueSet(code, u.getValue(), sys, problems)) { @@ -963,6 +995,7 @@ public class ValueSetValidator extends ValueSetProcessBase { */ private boolean checkSystems(List contains, String code, Set systems, List problems) { for (ValueSetExpansionContainsComponent c: contains) { + opContext.deadCheck(); if (c.getCode().equals(code)) { systems.add(c.getSystem()); } @@ -976,6 +1009,7 @@ public class ValueSetValidator extends ValueSetProcessBase { if (valueset == null) { return false; } + opContext.deadCheck(); checkCanonical(info.getIssues(), path, valueset, valueset); Boolean result = false; VersionInfo vi = new VersionInfo(this); @@ -1010,6 +1044,7 @@ public class ValueSetValidator extends ValueSetProcessBase { } private Boolean inComponent(String path, ConceptSetComponent vsi, int vsiIndex, String system, String version, String code, boolean only, ValidationProcessInfo info) throws FHIRException { + opContext.deadCheck(); boolean ok = true; if (vsi.hasValueSet()) { @@ -1189,6 +1224,7 @@ public class ValueSetValidator extends ValueSetProcessBase { } public boolean validateCodeInConceptList(String code, CodeSystem def, List list, AlternateCodesProcessingRules altCodeRules) { + opContext.deadCheck(); if (def.getCaseSensitive()) { for (ConceptDefinitionComponent cc : list) { if (cc.getCode().equals(code)) { @@ -1219,7 +1255,7 @@ public class ValueSetValidator extends ValueSetProcessBase { return inner.get(url); } ValueSet vs = context.fetchResource(ValueSet.class, url, valueset); - ValueSetValidator vsc = new ValueSetValidator(options, vs, context, localContext, expansionProfile, txCaps); + ValueSetValidator vsc = new ValueSetValidator(context, opContext.copy(), options, vs, localContext, expansionProfile, txCaps); vsc.setThrowToServer(throwToServer); inner.put(url, vsc); return vsc; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CommaSeparatedStringBuilder.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CommaSeparatedStringBuilder.java index ed1d3031d..57ac3959e 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CommaSeparatedStringBuilder.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CommaSeparatedStringBuilder.java @@ -117,4 +117,12 @@ public class CommaSeparatedStringBuilder { } return b.toString(); } + + public static String build(List list) { + CommaSeparatedStringBuilder self = new CommaSeparatedStringBuilder(); + for (String s : list) { + self.append(s); + } + return self.toString(); + } } \ No newline at end of file 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 a14be8625..5168dd1d1 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 @@ -866,6 +866,7 @@ public class I18nConstants { public static final String UNKNOWN_CODESYSTEM = "UNKNOWN_CODESYSTEM"; public static final String UNKNOWN_CODESYSTEM_VERSION = "UNKNOWN_CODESYSTEM_VERSION"; public static final String VALUESET_TOO_COSTLY = "VALUESET_TOO_COSTLY"; + public static final String VALUESET_TOO_COSTLY_TIME = "VALUESET_TOO_COSTLY_TIME"; public static final String NO_VALID_DISPLAY_FOUND = "NO_VALID_DISPLAY_FOUND"; public static final String SD_NO_CONTEXT_WHEN_NOT_EXTENSION = "SD_NO_CONTEXT_WHEN_NOT_EXTENSION"; public static final String SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = "SD_CONTEXT_SHOULD_NOT_BE_ELEMENT"; @@ -977,6 +978,8 @@ public class I18nConstants { public static final String CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO = "CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO"; public static final String CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG = "CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG"; public static final String CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED = "CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED"; + public static final String VALUESET_CIRCULAR_REFERENCE = "VALUESET_CIRCULAR_REFERENCE"; + public static final String VALUESET_SUPPLEMENT_MISSING = "VALUESET_SUPPLEMENT_MISSING"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index cbd93e2fc..7f1205a11 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -925,8 +925,9 @@ UNKNOWN_CODESYSTEM = The CodeSystem {0} is unknown UNKNOWN_CODESYSTEM_VERSION = The CodeSystem {0} version {1} is unknown. Valid versions: {2} UNABLE_TO_INFER_CODESYSTEM = The System URI could not be determined for the code {0} in the ValueSet {1} VALUESET_TOO_COSTLY = The value set {0} has too many codes to display ({1}) -NO_VALID_DISPLAY_FOUND_one = No valid Display Names found for {0}#{1} in the language {3} -NO_VALID_DISPLAY_FOUND_other = No valid Display Names found for {0}#{1} in the languages {3} +VALUESET_TOO_COSTLY_TIME = The value set {0} took too long to process (>{1}sec) +NO_VALID_DISPLAY_FOUND_one = No valid Display Names found for {1}#{2} in the language {4} +NO_VALID_DISPLAY_FOUND_other = No valid Display Names found for {1}#{2} in the languages {4} SD_NO_CONTEXT_WHEN_NOT_EXTENSION = The type is {0} so an extension context should not be specified SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION = The type is {0} so an extension context invariants should not be specified SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = Review the extension type: extensions should not have a context of {0} unless it''s really intended that they can be used anywhere @@ -1036,5 +1037,7 @@ CODESYSTEM_CS_COUNT_FRAGMENT_WRONG = The code system is a fragment/example, but CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO = The code system has no content, but the exceeds the stated total number is 0 concepts - check that this isn't a complete code system that has no concepts, or update/remove the stated count CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG = The code system supplement states the total number of concepts as {1}, but this is different to the underlying code system that states a value of {0} CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED = The code system says it has no content present, but concepts are found - +VALUESET_CIRCULAR_REFERENCE = Found a circularity pointing to {0} processing ValueSet with pathway {1} +VALUESET_SUPPLEMENT_MISSING_one = Required supplement not found: {1} +VALUESET_SUPPLEMENT_MISSING_other = Required supplements not found: {1} \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties b/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties index b30bbce6f..3485b2106 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties @@ -838,3 +838,6 @@ Display_Name_WS_for__should_be_one_of__instead_of_other = SD_ED_TYPE_PROFILE_WRONG_TYPE_one = SD_ED_TYPE_PROFILE_WRONG_TYPE_many = SD_ED_TYPE_PROFILE_WRONG_TYPE_other = +VALUESET_SUPPLEMENT_MISSING_one = +VALUESET_SUPPLEMENT_MISSING_many = +VALUESET_SUPPLEMENT_MISSING_other = diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java index 00c90d9fd..f7af6afda 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java @@ -46,8 +46,8 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader { private JsonObject test; } -// private static final String SERVER = FhirSettings.getTxFhirDevelopment(); - private static final String SERVER = FhirSettings.getTxFhirLocal(); + private static final String SERVER = FhirSettings.getTxFhirDevelopment(); +// private static final String SERVER = FhirSettings.getTxFhirLocal(); // private static final String SERVER = "https://r4.ontoserver.csiro.au/fhir"; From b8d89d253e6c5854a364ff2dcd10e8b2de77dd4e Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 19 Aug 2023 09:04:19 +1000 Subject: [PATCH 12/13] fix cross-version extensions URLs where possible --- .../java/org/hl7/fhir/r5/utils/XVerExtensionManager.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java index 6c5b94e3f..72f7b1a61 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java @@ -83,6 +83,7 @@ public class XVerExtensionManager { String verSource = url.substring(20, 23); String verTarget = VersionUtilities.getMajMin(context.getVersion()); String e = url.substring(54); + String r = e.contains(".") ? e.substring(0, e.indexOf(".")) : e; JsonObject root = lists.get(verSource); JsonObject path = root.getJsonObject(e); if (path == null) { @@ -91,7 +92,11 @@ public class XVerExtensionManager { StructureDefinition sd = new StructureDefinition(); sd.setUserData(XVER_EXT_MARKER, "true"); - sd.setWebPath(PackageHacker.fixPackageUrl("https://hl7.org/fhir/versions.html#extensions")); + if (context.getResourceNamesAsSet().contains(r)) { + sd.setWebPath(Utilities.pathURL(context.getSpecUrl(), r.toLowerCase()+"-definitions.html#"+e)); + } else { + sd.setWebPath(PackageHacker.fixPackageUrl("https://hl7.org/fhir/versions.html#extensions")); + } sd.setUrl(url); sd.setVersion(context.getVersion()); sd.setFhirVersion(FHIRVersion.fromCode(context.getVersion())); From 1248bae5e4a26cc9d274c880a0cd9934e36a2bc1 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 19 Aug 2023 09:13:32 +1000 Subject: [PATCH 13/13] $externals works like $fragments when no external string provided --- .../fhir/r5/test/utils/CompareUtilities.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java index 84da71c09..8ca51bde7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java @@ -42,17 +42,18 @@ public class CompareUtilities extends BaseTestingUtilities { private String presentExpected(String expected) { if (expected.startsWith("$") && expected.endsWith("$")) { if (expected.startsWith("$choice:")) { - return "Contains one of "+readChoices(8, expected).toString(); + return "Contains one of "+readChoices(expected.substring(8, expected.length()-1)).toString(); } else if (expected.startsWith("$fragments:")) { - List fragments = readChoices(11, expected); + List fragments = readChoices(expected.substring(11, expected.length()-1)); return "Contains all of "+fragments.toString(); } else if (expected.startsWith("$external:")) { - String[] cmd = expected.substring(1, expected.length() - 1).split("\\:"); + String[] cmd = expected.substring(1, expected.length() - 1).split(":"); if (externals != null) { String s = externals.asString(cmd[1]); return "\""+s+"\" (Ext)"; } else { - return "Contains \""+cmd[2]+"\""; + List fragments = readChoices(cmd[2]); + return "Contains all of "+fragments.toString()+" (because no external string provided for "+cmd[1]+")"; } } else { switch (expected) { @@ -408,10 +409,10 @@ private boolean isOptional(JsonElement e) { private boolean matches(String actualJsonString, String expectedJsonString) { if (expectedJsonString.startsWith("$") && expectedJsonString.endsWith("$")) { if (expectedJsonString.startsWith("$choice:")) { - return Utilities.existsInList(actualJsonString, readChoices(8, expectedJsonString)); + return Utilities.existsInList(actualJsonString, readChoices(expectedJsonString.substring(8, expectedJsonString.length()-1))); } else if (expectedJsonString.startsWith("$fragments:")) { - List fragments = readChoices(11, expectedJsonString); + List fragments = readChoices(expectedJsonString.substring(11, expectedJsonString.length()-1)); for (String f : fragments) { if (!actualJsonString.toLowerCase().contains(f.toLowerCase())) { return false; @@ -424,7 +425,13 @@ private boolean isOptional(JsonElement e) { String s = externals.asString(cmd[1]); return actualJsonString.equals(s); } else { - return actualJsonString.contains(cmd[2]); + List fragments = readChoices(cmd[2]); + for (String f : fragments) { + if (!actualJsonString.toLowerCase().contains(f.toLowerCase())) { + return false; + } + } + return true; } } else { switch (expectedJsonString) { @@ -440,9 +447,8 @@ private boolean isOptional(JsonElement e) { } } - private List readChoices(int offset, String s) { + private List readChoices(String s) { List list = new ArrayList<>(); - s = s.substring(offset, s.length()-1); for (String p : s.split("\\|")) { list.add(p); }