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/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.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(); 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.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..3653ab62a5 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlToMarkdownConverter.java @@ -0,0 +1,332 @@ +package org.hl7.fhir.utilities.xhtml; + +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) { + } + + 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); + } + } + } + +} 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