From 4f21ad96b26ad2b3d23bba47c4e0c31ae238c0fa Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 18 Dec 2024 06:16:37 +1100 Subject: [PATCH 1/5] Add support for finding the existence of implicit value sets --- .../fhir/r5/context/BaseWorkerContext.java | 48 +++++++++++++++++++ .../hl7/fhir/r5/context/IWorkerContext.java | 2 + .../client/TerminologyClientContext.java | 6 ++- .../client/TerminologyClientManager.java | 33 ++++++++++++- .../utilities/TerminologyCache.java | 7 +++ .../instance/type/ValueSetValidator.java | 23 +++++---- 6 files changed, 107 insertions(+), 12 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 f3513fef7b..95b7b93bec 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 @@ -905,6 +905,54 @@ public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean h return expandVS(vs, cacheOk, heirarchical, false, p); } + @Override + public ValueSetExpansionOutcome expandVS(String url, boolean cacheOk, boolean hierarchical, int count) { + if (expParameters == null) + throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED)); + if (noTerminologyServer) { + return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, null, false); + } + + Parameters p = expParameters.copy(); + p.addParameter("count", count); + p.addParameter("url", new UriType(url)); + p.setParameter("_limit",new IntegerType("10000")); + p.setParameter("_incomplete", new BooleanType("true")); + + CacheToken cacheToken = txCache.generateExpandToken(url, hierarchical); + ValueSetExpansionOutcome res; + if (cacheOk) { + res = txCache.getExpansion(cacheToken); + if (res != null) { + return res; + } + } + p.setParameter("excludeNested", !hierarchical); + List allErrors = new ArrayList<>(); + + p.addParameter().setName("cache-id").setValue(new IdType(terminologyClientManager.getCacheId())); + TerminologyClientContext tc = terminologyClientManager.chooseServer(url, true); + + txLog("$expand "+url+" on "+tc.getAddress()); + + try { + ValueSet result = tc.getClient().expandValueset(null, p); + if (result != null) { + if (!result.hasUrl()) { + result.setUrl(url); + } + if (!result.hasUrl()) { + throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2)); + } + } + res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); + } catch (Exception e) { + res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors, true).setTxLink(txLog == null ? null : txLog.getLastId()); + } + txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT); + return res; + } + @Override public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, boolean incompleteOk) { if (expParameters == null) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java index 46d8d8a6aa..6b00b3d4ed 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java @@ -493,6 +493,8 @@ public String getUrl() { */ public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical, boolean incompleteOk); + public ValueSetExpansionOutcome expandVS(String uri, boolean cacheOk, boolean heiarchical, int count); // set to 0 to just check existence + /** * ValueSet Expansion - see $expand, but resolves the binding first * diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientContext.java index 4d8c5d7ee3..c1edc2ceb1 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientContext.java @@ -85,8 +85,10 @@ public ITerminologyClient getClient() { } public void seeUse(Set systems, TerminologyClientContextUseType useType) { - for (String s : systems) { - seeUse(s, useType); + if (systems != null) { + for (String s : systems) { + seeUse(s, useType); + } } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java index 00897c519a..f4df2975fa 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java @@ -291,6 +291,37 @@ public TerminologyClientContext chooseServer(ValueSet vs, Set systems, b } } + public TerminologyClientContext chooseServer(String vs, boolean expand) throws TerminologyServiceException { + if (serverList.isEmpty()) { + return null; + } + if (IGNORE_TX_REGISTRY || !useEcosystem) { + return findClient(getMasterClient().getAddress(), null, expand); + } + String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&valueSet="+Utilities.URLEncode(vs)); + if (usage != null) { + request = request + "&usage="+usage; + } + try { + JsonObject json = JsonParser.parseObjectFromUrl(request); + for (JsonObject item : json.getJsonObjects("authoritative")) { + return findClient(item.asString("url"), null, expand); + } + for (JsonObject item : json.getJsonObjects("candidates")) { + return findClient(item.asString("url"), null, expand); + } + } catch (Exception e) { + String msg = "Error resolving valueSet "+vs+": "+e.getMessage(); + if (!hasMessage(msg)) { + internalLog.add(new InternalLogEvent(msg, vs, request)); + } + if (logger.isDebugLogging()) { + e.printStackTrace(); + } + } + return null; + } + private void log(ValueSet vs, String server, Set systems, List choices, String message) { String svs = (vs == null ? "null" : vs.getVersionedUrl()); String sys = systems.isEmpty() ? "--" : systems.size() == 1 ? systems.iterator().next() : systems.toString(); @@ -550,7 +581,7 @@ public SourcedValueSet findValueSetOnServer(String canonical) { } } if (server == null) { - return null; + server = getMasterClient().getAddress(); } if (server.contains("://tx.fhir.org")) { try { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java index 8340cb092d..eb31066c84 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java @@ -528,6 +528,13 @@ public CacheToken generateExpandToken(ValueSet vs, boolean hierarchical) { ct.key = String.valueOf(hashJson(ct.request)); return ct; } + + public CacheToken generateExpandToken(String url, boolean hierarchical) { + CacheToken ct = new CacheToken(); + ct.request = "{\"hierarchical\" : "+(hierarchical ? "true" : "false")+", \"url\": \""+Utilities.escapeJson(url)+"\"}\r\n"; + ct.key = String.valueOf(hashJson(ct.request)); + return ct; + } public void nameCacheToken(ValueSet vs, CacheToken ct) { if (vs != null) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java index 6ed9f5831a..05555971f6 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java @@ -18,6 +18,7 @@ import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; +import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest; import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; @@ -241,15 +242,19 @@ private boolean validateValueSetInclude(ValidationContext valContext, List Date: Wed, 18 Dec 2024 06:16:47 +1100 Subject: [PATCH 2/5] Start removing R2 support --- .../fhir/r5/context/SimpleWorkerContext.java | 3 + .../tests/ValidationEngineTests.java | 42 ------------- .../4.0.1/all-systems.cache | 60 ++++++++++++++++++- 3 files changed, 62 insertions(+), 43 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java index 0d0ef5f3cb..81ab9cb44c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java @@ -244,6 +244,9 @@ public SimpleWorkerContext build() throws IOException { } private SimpleWorkerContext build(SimpleWorkerContext context) throws IOException { + if (VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2Ver(context.getVersion())) { + System.out.println("As of end 2024, FHIR R2 (version "+context.getVersion()+") is no longer officially supported."); + } context.initTxCache(terminologyCachePath); context.setUserAgent(userAgent); context.setLogger(loggingService); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationEngineTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationEngineTests.java index 68c22b186d..4d7770ec68 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationEngineTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationEngineTests.java @@ -215,47 +215,6 @@ public void test140() throws Exception { verifyNoTerminologyRequests(logger); } - @Test - public void test102() throws Exception { - if (inbuild) { - Assertions.assertTrue(true); - return; - } - if (!org.hl7.fhir.validation.tests.utilities.TestUtilities.silent) - System.out.println("Test102: Validate patient-example.xml in v1.0.2 version"); - ValidationEngine ve = TestUtilities.getValidationEngine("hl7.fhir.r2.core#1.0.2", DEF_TX, FhirPublication.DSTU2, "1.0.2"); - ve.setNoInvariantChecks(true); - CacheVerificationLogger logger = new CacheVerificationLogger(); - ve.getContext().getTxClientManager().getMasterClient().setLogger(logger); - OperationOutcome op = ve.validate(FhirFormat.XML, TestingUtilities.loadTestResourceStream("validator", "patient102.xml"), null); - Assertions.assertTrue(checkOutcomes("test102", op, - "Patient.contact[0].name.family[0].extension[0].value.ofType(code) null error/code-invalid: The value provided ('VV') was not found in the value set 'EntityNamePartQualifier' (http://hl7.org/fhir/ValueSet/name-part-qualifier|1.0.2), and a code is required from this value set (error message = The System URI could not be determined for the code 'VV' in the ValueSet 'http://hl7.org/fhir/ValueSet/name-part-qualifier|1.0.2'; The provided code '#VV' was not found in the value set 'http://hl7.org/fhir/ValueSet/name-part-qualifier|1.0.2')")); - verifyNoTerminologyRequests(logger); - } - - @Test - public void testObs102() throws Exception { - if (inbuild) { - Assertions.assertTrue(true); - return; - } - if (!TestUtilities.silent) - System.out.println("TestObs102: Validate patient-example.xml in v1.0.2 version"); - ValidationEngine ve = TestUtilities.getValidationEngine("hl7.fhir.r2.core#1.0.2", DEF_TX, FhirPublication.DSTU2, "1.0.2"); - ve.setNoInvariantChecks(true); - CacheVerificationLogger logger = new CacheVerificationLogger(); - ve.getContext().getTxClientManager().getMasterClient().setLogger(logger); - OperationOutcome op = ve.validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", "observation102.json"), null); - Assertions.assertTrue(checkOutcomes("testObs102", op, - "Observation.text.div null error/invalid: Wrong namespace on the XHTML ('null', should be 'http://www.w3.org/1999/xhtml')\n"+ - "Observation.category null information/business-rule: Reference to experimental CodeSystem http://hl7.org/fhir/observation-category\n"+ - "Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+ - "Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have an effective[x] ()\n"+ - "Observation.code.coding[2].system null warning/not-found: A definition for CodeSystem 'http://acme.org/devices/clinical-codes' could not be found, so the code cannot be validated")); - verifyNoTerminologyRequests(logger); - } - - @Test public void test301() throws Exception { if (!TestUtilities.silent) @@ -321,7 +280,6 @@ public static void execute() throws Exception { ValidationEngineTests self = new ValidationEngineTests(); self.test401Xml(); self.test401Json(); - self.test102(); self.test140(); self.test301USCore(); System.out.println("Finished"); diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache index 046868ac5b..17aeeb73d0 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache @@ -6309,10 +6309,68 @@ v: { "code" : "001", "system" : "http://unstats.un.org/unsd/methods/m49/m49.htm", "server" : "http://tx-dev.fhir.org/r4", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "urn:oid:1.2.840.114350.1.72.1.7.7.10.696784.13260", + "code" : "1", + "display" : "Surgery Case" +}, "url": "http://terminology.hl7.org/ValueSet/v3-ActEncounterCode", "version": "3.0.0", "langs":"en-US", "useServer":"true", "useClient":"true", "guessSystem":"false", "activeOnly":"false", "membershipOnly":"false", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "code" : "1", + "system" : "urn:oid:1.2.840.114350.1.72.1.7.7.10.696784.13260", + "severity" : "error", + "error" : "A definition for CodeSystem 'urn:oid:1.2.840.114350.1.72.1.7.7.10.696784.13260' could not be found, so the code cannot be validated; The provided code 'urn:oid:1.2.840.114350.1.72.1.7.7.10.696784.13260#1 ('Surgery Case')' was not found in the value set 'http://terminology.hl7.org/ValueSet/v3-ActEncounterCode|3.0.0'", + "class" : "UNKNOWN", + "server" : "http://tx-dev.fhir.org/r4", + "unknown-systems" : "urn:oid:1.2.840.114350.1.72.1.7.7.10.696784.13260", + "issues" : { + "resourceType" : "OperationOutcome", + "issue" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-server", + "valueUrl" : "http://tx-dev.fhir.org/r4" + }], + "severity" : "error", + "code" : "not-found", + "details" : { + "coding" : [{ + "system" : "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code" : "not-found" + }], + "text" : "A definition for CodeSystem 'urn:oid:1.2.840.114350.1.72.1.7.7.10.696784.13260' could not be found, so the code cannot be validated" + }, + "location" : ["Coding.system"], + "expression" : ["Coding.system"] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-server", + "valueUrl" : "http://tx-dev.fhir.org/r4" + }], + "severity" : "error", + "code" : "code-invalid", + "details" : { + "coding" : [{ + "system" : "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code" : "not-in-vs" + }], + "text" : "The provided code 'urn:oid:1.2.840.114350.1.72.1.7.7.10.696784.13260#1 ('Surgery Case')' was not found in the value set 'http://terminology.hl7.org/ValueSet/v3-ActEncounterCode|3.0.0'" + }, + "location" : ["Coding.code"], + "expression" : ["Coding.code"] + }] +} + +} +------------------------------------------------------------------------------------- From 8a7b7ea026235ffb78aafb8353b8bf66ceee362d Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 18 Dec 2024 06:17:04 +1100 Subject: [PATCH 3/5] Add support for THO rendering of NamingSystem information --- .../r5/renderers/NamingSystemRenderer.java | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java index 57844b05be..444a60ac98 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; @@ -70,16 +72,25 @@ public void render(RenderingStatus status, XhtmlNode x, NamingSystem ns) throws if (ns.hasCopyright()) { addMarkdown(row(tbl, (context.formatPhrase(RenderingContext.GENERAL_COPYRIGHT))), ns.getCopyright()); } + List nsl = new ArrayList<>(); + nsl.add(ns); + renderList(x, nsl); + } + + public void renderList(XhtmlNode x, List nsl) { + boolean hasPreferred = false; boolean hasPeriod = false; boolean hasComment = false; - for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { - hasPreferred = hasPreferred || id.hasPreferred(); - hasPeriod = hasPeriod || id.hasPeriod(); - hasComment = hasComment || id.hasComment(); + for (NamingSystem ns : nsl) { + for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { + hasPreferred = hasPreferred || id.hasPreferred(); + hasPeriod = hasPeriod || id.hasPeriod(); + hasComment = hasComment || id.hasComment(); + } } x.h3().tx(context.formatPhrase(RenderingContext.NAME_SYS_IDEN)); - tbl = x.table("grid"); + XhtmlNode tbl = x.table("grid"); XhtmlNode tr = tbl.tr(); tr.td().b().tx((context.formatPhrase(RenderingContext.GENERAL_TYPE))); tr.td().b().tx((context.formatPhrase(RenderingContext.GENERAL_VALUE))); @@ -92,21 +103,24 @@ public void render(RenderingStatus status, XhtmlNode x, NamingSystem ns) throws if (hasComment) { tr.td().b().tx((context.formatPhrase(RenderingContext.GENERAL_COMMENT))); } - for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { - tr = tbl.tr(); - tr.td().tx(id.getType().getDisplay()); - tr.td().tx(id.getValue()); - if (hasPreferred) { - tr.td().tx(id.getPreferredElement().primitiveValue()); - } - if (hasPeriod) { - tr.td().tx(displayDataType(id.getPeriod())); - } - if (hasComment) { - tr.td().tx(id.getComment()); - } - } + for (NamingSystem ns : nsl) { + for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { + tr = tbl.tr(); + tr.td().tx(id.getType().getDisplay()); + tr.td().tx(id.getValue()); + if (hasPreferred) { + tr.td().tx(id.getPreferredElement().primitiveValue()); + } + if (hasPeriod) { + tr.td().tx(displayDataType(id.getPeriod())); + } + if (hasComment) { + tr.td().tx(id.getComment()); + } + } + } } + private XhtmlNode row(XhtmlNode tbl, String name) { XhtmlNode tr = tbl.tr(); From a703145ffc0b2d19a89c14b66c14b3bd2bce7a8e Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 18 Dec 2024 06:17:20 +1100 Subject: [PATCH 4/5] Add XhtmlToMarkdown library --- .../xhtml/XhtmlToMarkdownConverter.java | 334 ++++++++++++++++++ 1 file changed, 334 insertions(+) create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java new file mode 100644 index 0000000000..4f955cabf0 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java @@ -0,0 +1,334 @@ +package org.hl7.fhir.utilities.xhtml; + +import org.hl7.fhir.utilities.DebugUtilities; +import org.hl7.fhir.utilities.Utilities; + +public class XhtmlToMarkdownConverter { + + public String convert(XhtmlNode x) { + StringBuilder b = new StringBuilder(); + convert(b, x); + return b.toString(); + } + + private void convert(StringBuilder b, XhtmlNode x) { + for (XhtmlNode c : x.getChildNodes()) { + if (c.getNodeType() == NodeType.Text) { + paragraph(b, c, true, false); + } else if (c.getName() != null) { + convertNode(b, c); + } + } + + } + + public void convertNode(StringBuilder b, XhtmlNode c) throws Error { + switch (c.getName()) { + case "p": + paragraph(b, c, true, false); + break; + case "hr": + b.append("-------\r\n"); + break; + case "a": + paragraph(b, c, true, false); + break; + case "h1": + b.append("# "+c.allText()); + break; + case "h2": + b.append("## "+c.allText()); + break; + case "h3": + b.append("### "+c.allText()); + break; + case "h4": + b.append("#### "+c.allText()); + break; + case "h5": + b.append("##### "+c.allText()); + break; + case "h6": + b.append("###### "+c.allText()); + break; + case "strong": + case "em": + case "i": + case "span": + case "img": + case "br": + case "wbr": + case "code": + case "del": + case "sup": + case "sub": + case "u": + case "b": + paragraph(b, c, true, false); + break; + case "div": + convert(b, c); + break; + case "ul": + convertUL(b, c); + break; + case "li": + convertLI(b, c); + break; + case "ol": + convertOL(b, c); + break; + case "table": + case "tbody": + convertTable(b, c); + break; + case "iframe": + convertIFrame(b, c); + break; + case "form": + case "select": + case "input": + case "textarea": + case "option": + convertForm(b, c); + break; + case "label": + case "cite": + convertLabel(b, c); + break; + case "figure": + case "figcaption": + convert(b, c); + break; + case "dl": + case "tt": + convertDL(b, c); + break; + case "blockquote": + b.append("\r\n"); + b.append("> "); + paragraph(b, c, false, true); + b.append("\r\n"); + break; + case "pre": + b.append("````\r\n"); + convert(b, c); + b.append("\r\n````\r\n"); + break; + default: + throw new Error("not done yet: "+c.getName()); + } + } + + private void convertDL(StringBuilder b, XhtmlNode c) { + DebugUtilities.breakpoint(); + } + + private void convertIFrame(StringBuilder b, XhtmlNode c) { + b.append(c.toString()); + } + + private void convertForm(StringBuilder b, XhtmlNode c) { + b.append(c.toString()); + } + + private void convertLabel(StringBuilder b, XhtmlNode c) { + convert(b, c); + } + + private void convertTable(StringBuilder b, XhtmlNode x) { + boolean first = true; + for (XhtmlNode c : x.getChildNodes()) { + if ("tbody".equals(c.getName())) { + convertTable(b, c); + } else if ("tr".equals(c.getName())) { + if (first) { + first = false; + b.append("\r\n"); + } + boolean header = false; + for (XhtmlNode g : c.getChildNodes()) { + if (Utilities.existsInList(g.getName(), "td", "th")) { + if ("th".equals(g.getName())) { + header = true; + } + b.append("|"); + paragraph(b, g, false, true); + } + } + b.append("\r\n"); + if (header) { + for (XhtmlNode g : c.getChildNodes()) { + if (Utilities.existsInList(g.getName(), "td", "th")) { + b.append("|---"); + } + } + b.append("\r\n"); + } + } + } + + } + + private void convertUL(StringBuilder b, XhtmlNode x) { + + for (XhtmlNode c : x.getChildNodes()) { + if ("li".equals(c.getName())) { + b.append("* "); + paragraph(b, c, true, true); + } + } + } + + private void convertLI(StringBuilder b, XhtmlNode x) { + b.append("* "); + paragraph(b, x, true, true); + } + + private void convertOL(StringBuilder b, XhtmlNode x) { + + for (XhtmlNode c : x.getChildNodes()) { + if ("li".equals(c.getName())) { + b.append("1. "); + paragraph(b, c, true, true); + } + } + } + + private void paragraph(StringBuilder b, XhtmlNode x, boolean mark, boolean trim) { + if (x.getNodeType() == NodeType.Text) { + addText(b, trim, x.getContent()); + } else if (x.getName() != null && !Utilities.existsInList(x.getName(), "p", "li", "td", "th", "blockquote")) { + part(b, x, trim); + } else { + for (XhtmlNode c : x.getChildNodes()) { + if (c.getNodeType() == NodeType.Text) { + addText(b, trim, c.getContent()); + } else { + part(b, c, trim); + } + } + if (mark) { + b.append("\r\n"); + } + } + } + + private void addText(StringBuilder b, boolean trim, String content) { + if (trim) { + content = content.trim(); + } + if (!Utilities.noString(content)) { + b.append(content); + } + + } + + public void part(StringBuilder b, XhtmlNode c, boolean trim) throws Error { + switch (c.getName()) { + case "a": + b.append("["); + process(b, c, trim); + b.append("]("); + b.append(c.getAttribute("href")); + b.append(")"); + break; + case "img": + b.append("!["); + b.append(c.allText()); + b.append("]("); + b.append(c.getAttribute("src")); + b.append(")"); + break; + case "code": + case "pre": + b.append("```"); + process(b, c, trim); + b.append("```"); + case "strong": + case "em": + case "b": + b.append("**"); + process(b, c, trim); + b.append("**"); + break; + case "u": + b.append("_"); + process(b, c, trim); + b.append("_"); + break; + case "i": + b.append("*"); + process(b, c, trim); + b.append("*"); + break; + case "span": + case "sup": + case "sub": + case "s": + process(b, c, trim); + break; + case "div": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + case "table": + case "cite": + b.append("\r\n"); + convertNode(b, c); + break; + case "p": + if (!trim) { + b.append("\r\n\r\n"); + } + process(b, c, trim); + break; + case "ul": + b.append("\r\n"); + convertUL(b, c); + break; + case "li": + b.append("\r\n"); + convertLI(b, c); + break; + case "ol": + b.append("\r\n"); + convertOL(b, c); + break; + case "blockquote": + b.append("\r\n"); + b.append("> "); + paragraph(b, c, false, trim); + b.append("\r\n"); + break; + case "del": + case "dl": + case "dd": + case "tt": + process(b, c, trim); + case "br": + b.append("\r\n"); + process(b, c, trim); + break; + case "wbr": + b.append("\r\n"); + break; + default: + throw new Error("part not done yet: "+c.getName()); + } + } + + private void process(StringBuilder b, XhtmlNode x, boolean trim) { + for (XhtmlNode c : x.getChildNodes()) { + if (c.getNodeType() == NodeType.Text) { + b.append(c.getContent()); + } else if (c.getName() != null) { + part(b, c, trim); + } + } + } + +} From eb4981c8436c20bf87d59b24352efd170086845b Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 18 Dec 2024 06:23:22 +1100 Subject: [PATCH 5/5] compile fix --- .../org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java index 4f955cabf0..3653ab62a5 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java @@ -1,6 +1,5 @@ package org.hl7.fhir.utilities.xhtml; -import org.hl7.fhir.utilities.DebugUtilities; import org.hl7.fhir.utilities.Utilities; public class XhtmlToMarkdownConverter { @@ -121,7 +120,6 @@ public void convertNode(StringBuilder b, XhtmlNode c) throws Error { } private void convertDL(StringBuilder b, XhtmlNode c) { - DebugUtilities.breakpoint(); } private void convertIFrame(StringBuilder b, XhtmlNode c) {