
001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.Collections; 007import java.util.List; 008 009import org.hl7.fhir.exceptions.DefinitionException; 010import org.hl7.fhir.exceptions.FHIRException; 011import org.hl7.fhir.exceptions.FHIRFormatError; 012import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation; 013import org.hl7.fhir.r5.model.BooleanType; 014import org.hl7.fhir.r5.model.CanonicalResource; 015import org.hl7.fhir.r5.model.CodeSystem; 016import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent; 017import org.hl7.fhir.r5.model.CodeSystem.CodeSystemHierarchyMeaning; 018import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 019import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 020import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 021import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent; 022import org.hl7.fhir.r5.model.Coding; 023import org.hl7.fhir.r5.model.Enumeration; 024import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; 025import org.hl7.fhir.r5.model.Extension; 026import org.hl7.fhir.r5.model.PrimitiveType; 027import org.hl7.fhir.r5.model.Resource; 028import org.hl7.fhir.r5.model.StringType; 029import org.hl7.fhir.r5.model.ValueSet; 030import org.hl7.fhir.r5.renderers.utils.RenderingContext; 031import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; 032import org.hl7.fhir.r5.renderers.utils.RenderingContext.MultiLanguagePolicy; 033import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 034import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 035import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.CodeSystemNavigator; 036import org.hl7.fhir.r5.utils.EOperationOutcome; 037import org.hl7.fhir.r5.utils.ToolingExtensions; 038import org.hl7.fhir.r5.utils.UserDataNames; 039import org.hl7.fhir.utilities.LoincLinker; 040import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 041import org.hl7.fhir.utilities.Utilities; 042import org.hl7.fhir.utilities.xhtml.XhtmlNode; 043 044@MarkedToMoveToAdjunctPackage 045public class CodeSystemRenderer extends TerminologyRenderer { 046 047 048 public CodeSystemRenderer(RenderingContext context) { 049 super(context); 050 } 051 052 @Override 053 public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 054 if (r.isDirect()) { 055 renderResourceTechDetails(r, x); 056 genSummaryTable(status, x, (CodeSystem) r.getBase()); 057 render(status, x, (CodeSystem) r.getBase(), r); 058 } else { 059 // the intention is to change this in the future 060 x.para().tx("CodeSystemRenderer only renders native resources directly"); 061 } 062 } 063 064 @Override 065 public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 066 return canonicalTitle(r); 067 } 068 069 070 public class Translateable { 071 072 private String lang; 073 private StringType value; 074 075 public Translateable(String lang, StringType value) { 076 this.lang = lang; 077 this.value = value; 078 } 079 080 public String getLang() { 081 return lang; 082 } 083 084 public StringType getValue() { 085 return value; 086 } 087 088 } 089 090 private Boolean doMarkdown = null; 091 092 public void render(RenderingStatus status, XhtmlNode x, CodeSystem cs, ResourceWrapper res) throws FHIRFormatError, DefinitionException, IOException { 093 094 if (context.isShowSummaryTable()) { 095 XhtmlNode h = x.h2(); 096 h.addText(cs.hasTitle() ? cs.getTitle() : cs.getName()); 097 addMarkdown(x, cs.getDescription()); 098 if (cs.hasCopyright()) 099 generateCopyright(x, res); 100 } 101 102 boolean props = generateProperties(x, cs); 103 generateFilters(x, cs); 104 List<UsedConceptMap> maps = new ArrayList<UsedConceptMap>(); 105 generateCodeSystemContent(status, x, cs, maps, props); 106 } 107 108 public void describe(XhtmlNode x, CodeSystem cs) { 109 x.tx(display(cs)); 110 } 111 112 public String display(CodeSystem cs) { 113 return cs.present(); 114 } 115 116 private void generateFilters(XhtmlNode x, CodeSystem cs) { 117 if (cs.hasFilter()) { 118 x.para().b().tx(formatPhrase(RenderingContext.CODESYSTEM_FILTERS)); 119 XhtmlNode tbl = x.table("grid", false); 120 XhtmlNode tr = tbl.tr(); 121 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_CODE)); 122 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_DESC)); 123 tr.td().b().tx(formatPhrase(RenderingContext.CODESYSTEM_FILTER_OP)); 124 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_VALUE)); 125 for (CodeSystemFilterComponent f : cs.getFilter()) { 126 tr = tbl.tr(); 127 renderStatus(f, tr.td()).tx(f.getCode()); 128 renderStatus(f.getDescriptionElement(), tr.td()).tx(f.getDescription()); 129 XhtmlNode td = tr.td(); 130 for (Enumeration<org.hl7.fhir.r5.model.Enumerations.FilterOperator> t : f.getOperator()) 131 renderStatus(t, td).tx(t.asStringValue()+" "); 132 renderStatus(f.getValueElement(), tr.td()).tx(f.getValue()); 133 } 134 } 135 } 136 137 private boolean generateProperties(XhtmlNode x, CodeSystem cs) { 138 if (cs.hasProperty()) { 139 boolean hasRendered = false; 140 boolean hasURI = false; 141 boolean hasDescription = false; 142 boolean hasValueSet = false; 143 for (PropertyComponent p : cs.getProperty()) { 144 hasRendered = hasRendered || getDisplayForProperty(p) != null; 145 hasURI = hasURI || p.hasUri(); 146 hasDescription = hasDescription || p.hasDescription(); 147 hasValueSet = hasValueSet || p.hasExtension(ToolingExtensions.EXT_PROPERTY_VALUESET); 148 } 149 150 x.para().b().tx(formatPhrase(RenderingContext.GENERAL_PROPS)); 151 x.para().b().tx(formatPhrase(RenderingContext.CODESYSTEM_PROPS_DESC)); 152 XhtmlNode tbl = x.table("grid", false); 153 XhtmlNode tr = tbl.tr(); 154 if (hasRendered) { 155 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_NAME)); 156 } 157 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_CODE)); 158 if (hasURI) { 159 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_URI)); 160 } 161 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_TYPE)); 162 if (hasDescription) { 163 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_DESC)); 164 } 165 if (hasValueSet) { 166 tr.td().b().tx(formatPhrase(RenderingContext.GENERAL_VALUESET)); 167 } 168 for (PropertyComponent p : cs.getProperty()) { 169 tr = tbl.tr(); 170 if (hasRendered) { 171 tr.td().tx(getDisplayForProperty(p)); 172 } 173 renderStatus(p, tr.td()).tx(p.getCode()); 174 if (hasURI) { 175 renderStatus(p.getUriElement(), tr.td()).tx(p.getUri()); 176 } 177 renderStatus(p.getTypeElement(), tr.td()).tx(p.hasType() ? p.getType().toCode() : ""); 178 if (hasDescription) { 179 renderStatus(p.getDescriptionElement(), tr.td()).tx(p.getDescription()); 180 } 181 if (hasValueSet) { 182 XhtmlNode td = tr.td(); 183 String url = p.getExtensionString(ToolingExtensions.EXT_PROPERTY_VALUESET); 184 if (url != null) { 185 ValueSet vs = context.getContext().fetchResource(ValueSet.class, url); 186 if (vs == null) { 187 td.code().tx(url); 188 } else { 189 td.ah(vs.getWebPath()).tx(vs.present()); 190 } 191 } 192 } 193 } 194 return true; 195 } else { 196 return false; 197 } 198 } 199 200 private String sentenceForContent(CodeSystemContentMode mode, CodeSystem cs) { 201 if (mode == null) { 202 return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_NOTPRESENT); 203 } 204 switch (mode) { 205 case COMPLETE: return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_COMPLETE); 206 case EXAMPLE: return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_EXAMPLE); 207 case FRAGMENT: return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_FRAGMENT); 208 case NOTPRESENT: return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_NOTPRESENT); 209 case SUPPLEMENT: 210 boolean properties = CodeSystemUtilities.hasProperties(cs); 211 boolean designations = CodeSystemUtilities.hasDesignations(cs); 212 String features; 213 if (properties && designations) { 214 features = (context.formatPhrase(RenderingContext.CODE_SYS_DISP_PROP)); 215 } else if (properties) { 216 features = (context.formatPhrase(RenderingContext.CODE_SYS_PROP)); 217 } else if (designations) { 218 features = (context.formatPhrase(RenderingContext.CODE_SYS_DISP)); 219 } else { 220 features = (context.formatPhrase(RenderingContext.CODE_SYS_FEAT)); // ? 221 } 222 return formatPhrase(RenderingContext.CODESYSTEM_CONTENT_SUPPLEMENT, features); 223 default: 224 throw new FHIRException(context.formatPhrase(RenderingContext.CODE_SYS_UNKN_MODE)); 225 } 226 } 227 228 private void generateCodeSystemContent(RenderingStatus status, XhtmlNode x, CodeSystem cs, List<UsedConceptMap> maps, boolean props) throws FHIRFormatError, DefinitionException, IOException { 229 if (props) { 230 x.para().b().tx(formatPhrase(RenderingContext.CODESYSTEM_CONCEPTS)); 231 } 232 XhtmlNode p = x.para(); 233 234 p.startScript("csc"); 235 renderStatus(cs.getUrlElement(), p.param("cs")).code().tx(cs.getUrl()); 236 makeCasedParam(p.param("cased"), cs, cs.getCaseSensitiveElement()); 237 makeHierarchyParam(p.param("h"), cs, cs.getHierarchyMeaningElement()); 238 p.paramValue("code-count", CodeSystemUtilities.countCodes(cs)); 239 p.execScript(sentenceForContent(cs.getContent(), cs)); 240 p.closeScript(); 241 242 if (cs.getContent() == CodeSystemContentMode.NOTPRESENT) { 243 return; 244 } 245 246 XhtmlNode t = x.table( "codes", false); 247 boolean definitions = false; 248 boolean commentS = false; 249 boolean deprecated = false; 250 boolean display = false; 251 boolean hierarchy = false; 252 boolean version = false; 253 boolean ignoreStatus = false; 254 boolean isSupplement = cs.getContent() == CodeSystemContentMode.SUPPLEMENT; 255 List<PropertyComponent> properties = new ArrayList<>(); 256 for (PropertyComponent cp : cs.getProperty()) { 257 if (showPropertyInTable(cp)) { 258 boolean exists = false; 259 for (ConceptDefinitionComponent c : cs.getConcept()) { 260 exists = exists || conceptsHaveProperty(c, cp); 261 } 262 if (exists) { 263 properties.add(cp); 264 if ("status".equals(cp.getCode())) { 265 ignoreStatus = true; 266 } 267 } 268 } 269 } 270 List<String> langs = new ArrayList<>(); 271 for (ConceptDefinitionComponent c : cs.getConcept()) { 272 commentS = commentS || conceptsHaveComments(c); 273 deprecated = deprecated || conceptsHaveDeprecated(cs, c, ignoreStatus); 274 display = display || conceptsHaveDisplay(c); 275 version = version || conceptsHaveVersion(c); 276 hierarchy = hierarchy || c.hasConcept(); 277 definitions = definitions || conceptsHaveDefinition(c); 278 listConceptLanguages(cs, c, langs); 279 } 280 CodeSystemNavigator csNav = new CodeSystemNavigator(cs); 281 hierarchy = hierarchy || csNav.isRestructure(); 282 283 if (langs.size() < 2) { 284 addCopyColumn(addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, definitions, commentS, version, deprecated, properties, langs, null, true), maps)); 285 } else { 286 addCopyColumn(addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, definitions, commentS, version, deprecated, properties, null, null, false), maps)); 287 } 288 for (ConceptDefinitionComponent c : csNav.getConcepts(null)) { 289 addDefineRowToTable(status, t, c, 0, hierarchy, display, definitions, commentS, version, deprecated, maps, cs.getUrl(), cs, properties, csNav, langs.size() < 2 ? langs : null, isSupplement); 290 } 291 if (langs.size() >= 2) { 292 Collections.sort(langs); 293 x.para().b().tx(context.formatPhrase(RenderingContext.GENERAL_ADD_LANG)); 294 t = x.table("codes", false); 295 XhtmlNode tr = t.tr(); 296 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 297 for (String lang : langs) 298 tr.td().b().addText(describeLang(lang)); 299 for (ConceptDefinitionComponent c : cs.getConcept()) { 300 addLanguageRow(c, t, langs); 301 } 302 } 303 } 304 305 private void makeHierarchyParam(XhtmlNode x, CodeSystem cs, Enumeration<CodeSystemHierarchyMeaning> hm) { 306 if (hm.hasValue()) { 307 String s = hm.getValue().getDisplay(); 308 renderStatus(hm, x).tx(" "+context.formatPhrase(RenderingContext.CODE_SYS_IN_A_HIERARCHY, s)); 309 } else if (VersionComparisonAnnotation.hasDeleted(cs, "hierarchyMeaning")) { 310 makeHierarchyParam(x, null, (Enumeration<CodeSystemHierarchyMeaning>) VersionComparisonAnnotation.getDeleted(cs, "hierarchyMeaning").get(0)); 311 } else if (CodeSystemUtilities.hasHierarchy(cs)) { 312 x.tx(" "+ (context.formatPhrase(RenderingContext.CODE_SYS_UNDEF_HIER))); 313 } else { 314 x.tx(""); 315 } 316 } 317 318 private void makeCasedParam(XhtmlNode x, CodeSystem cs, BooleanType caseSensitiveElement) { 319 if (caseSensitiveElement.hasValue()) { 320 String s = caseSensitiveElement.getValue() == true? "case-sensitive" : "case-insensitive"; 321 renderStatus(caseSensitiveElement, x).tx(s); 322 } else if (VersionComparisonAnnotation.hasDeleted(cs, "caseSensitive")) { 323 makeCasedParam(x, null, (BooleanType) VersionComparisonAnnotation.getDeleted(cs, "caseSensitive").get(0)); 324 } else { 325 x.tx(""); 326 } 327 } 328 329 private void listConceptLanguages(CodeSystem cs, ConceptDefinitionComponent c, List<String> langs) { 330 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 331 if (cd.hasLanguage() && !langs.contains(cd.getLanguage()) && (!cs.hasLanguage() || !cs.getLanguage().equals(cd.getLanguage()))) { 332 langs.add(cd.getLanguage()); 333 } 334 } 335 336 for (ConceptDefinitionComponent g : c.getConcept()) { 337 listConceptLanguages(cs, g, langs); 338 } 339 } 340 341 private void addCopyColumn(XhtmlNode tr) { 342 if (context.isCopyButton()) { 343 tr.td().b().tx(context.formatPhrase(RenderingContext.CODE_SYS_COPY)); 344 } 345 346 } 347 348 private boolean conceptsHaveDefinition(ConceptDefinitionComponent c) { 349 if (c.hasDefinition()) { 350 return true; 351 } 352 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 353 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) { 354 return true; 355 } 356 } 357 for (ConceptDefinitionComponent g : c.getConcept()) { 358 if (conceptsHaveDefinition(g)) { 359 return true; 360 } 361 } 362 return false; 363 } 364 365 private boolean conceptsHaveProperty(ConceptDefinitionComponent c, PropertyComponent cp) { 366 if (CodeSystemUtilities.hasProperty(c, cp.getCode())) 367 return true; 368 for (ConceptDefinitionComponent g : c.getConcept()) 369 if (conceptsHaveProperty(g, cp)) 370 return true; 371 return false; 372 373 } 374 375 private boolean showPropertyInTable(PropertyComponent cp) { 376 return cp.hasCode(); 377// if (cp.hasExtension(ToolingExtensions.EXT_RENDERED_VALUE)) { 378// return true; 379// } 380// if (cp.getCodeElement().hasExtension(ToolingExtensions.EXT_RENDERED_VALUE)) { 381// return true; 382// } 383// String uri = cp.getUri(); 384// if (Utilities.noString(uri)){ 385// return true; // do we always want to render properties in this case? Not sure... 386// } 387// String code = null; 388// if (uri.contains("#")) { 389// code = uri.substring(uri.indexOf("#")+1); 390// uri = uri.substring(0, uri.indexOf("#")); 391// } 392// if (Utilities.existsInList(uri, "http://hl7.org/fhir/concept-properties") || context.getCodeSystemPropList().contains(uri)) { 393// return true; 394// }; 395// CodeSystem cs = getContext().getWorker().fetchCodeSystem(uri); 396// if (cs == null) { 397// return false; 398// } 399// switch () 400// return code == null ? false : CodeSystemUtilities.hasCode(cs, code); 401 } 402 403 404 private int countConcepts(List<ConceptDefinitionComponent> list) { 405 int count = list.size(); 406 for (ConceptDefinitionComponent c : list) 407 if (c.hasConcept()) 408 count = count + countConcepts(c.getConcept()); 409 return count; 410 } 411 412 private boolean conceptsHaveComments(ConceptDefinitionComponent c) { 413 if (ToolingExtensions.hasCSComment(c)) 414 return true; 415 for (ConceptDefinitionComponent g : c.getConcept()) 416 if (conceptsHaveComments(g)) 417 return true; 418 return false; 419 } 420 421 private boolean conceptsHaveDisplay(ConceptDefinitionComponent c) { 422 if (c.hasDisplay() && !c.getDisplay().equals(c.getCode())) 423 return true; 424 for (ConceptDefinitionComponent g : c.getConcept()) 425 if (conceptsHaveDisplay(g)) 426 return true; 427 return false; 428 } 429 430 private boolean conceptsHaveVersion(ConceptDefinitionComponent c) { 431 if (c.hasUserData(UserDataNames.tx_cs_version_notes)) 432 return true; 433 for (ConceptDefinitionComponent g : c.getConcept()) 434 if (conceptsHaveVersion(g)) 435 return true; 436 return false; 437 } 438 439 private boolean conceptsHaveDeprecated(CodeSystem cs, ConceptDefinitionComponent c, boolean ignoreStatus) { 440 if (CodeSystemUtilities.isDeprecated(cs, c, ignoreStatus)) 441 return true; 442 for (ConceptDefinitionComponent g : c.getConcept()) 443 if (conceptsHaveDeprecated(cs, g, ignoreStatus)) 444 return true; 445 return false; 446 } 447 448 449 450 private void addDefineRowToTable(RenderingStatus status, XhtmlNode t, ConceptDefinitionComponent c, int level, boolean hasHierarchy, boolean hasDisplay, boolean hasDefinitions, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, List<PropertyComponent> properties, CodeSystemNavigator csNav, List<String> langs, boolean isSupplement) throws FHIRFormatError, DefinitionException, IOException { 451 boolean hasExtensions = false; 452 XhtmlNode tr = t.tr(); 453 boolean notCurrent = CodeSystemUtilities.isNotCurrent(cs, c); 454 if (notCurrent) { 455 tr.setAttribute("style", "background-color: #ffeeee"); 456 } 457 458 XhtmlNode td = renderStatusRow(c, t, tr); 459 if (hasHierarchy) { 460 td.addText(Integer.toString(level+1)); 461 td = tr.td(); 462 String s = Utilities.padLeft("", '\u00A0', level*2); 463 td.addText(s); 464 } 465 String link = isSupplement ? getLinkForCode(cs.getSupplements(), null, c.getCode()) : null; 466 if (link != null) { 467 td.ah(context.prefixLocalHref(link)).style( "white-space:nowrap").addText(c.getCode()); 468 } else { 469 td.style("white-space:nowrap").addText(c.getCode()); 470 } 471 XhtmlNode a; 472 if (c.hasCodeElement()) { 473 td.an(context.prefixAnchor(cs.getId()+"-" + Utilities.nmtokenize(c.getCode()))); 474 } 475 476 if (hasDisplay) { 477 td = tr.td(); 478 hasExtensions = renderDisplayName(c, cs, td, langs) || hasExtensions; 479 } 480 if (hasDefinitions) { 481 td = tr.td(); 482 if (c != null &&c.hasDefinitionElement()) { 483 // translations of the definition might come from either the translation extension, or from the designations 484 StringType defn = context.getTranslatedElement(c.getDefinitionElement()); 485 boolean sl = false; 486 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 487 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) { 488 sl = true; 489 } 490 } 491 492 if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE || !(sl || ToolingExtensions.hasLanguageTranslations(defn))) { 493 if (hasMarkdownInDefinitions(cs)) { 494 addMarkdown(renderStatusDiv(defn, td), defn.asStringValue()); 495 } else { 496 renderStatus(defn, td).addText(defn.asStringValue()); 497 } 498 } else { 499 List<Translateable> list = new ArrayList<>(); 500 list.add(new Translateable(cs.getLanguage(), defn)); 501 for (Extension ext : defn.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) { 502 hasExtensions = true; 503 list.add(new Translateable(ext.getExtensionString("lang"), ext.getExtensionByUrl("content").getValueStringType())); 504 } 505 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 506 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) { 507 list.add(new Translateable(cd.getLanguage(), cd.getValueElement())); 508 } 509 } 510 boolean first = true; 511 for (Translateable ti : list) { 512 if (first) { 513 first = false; 514 } else { 515 td.br(); 516 } 517 518 if (ti.lang != null) { 519 td.addText(ti.lang + ": "); 520 } 521 if (hasMarkdownInDefinitions(cs)) { 522 addMarkdown(renderStatusDiv(ti.getValue(), td), ti.getValue().asStringValue()); 523 } else { 524 renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue()); 525 } 526 } 527 } 528 } 529 } 530 if (deprecated) { 531 td = tr.td(); 532 Boolean b = CodeSystemUtilities.isDeprecated(cs, c, false); 533 if (b != null && b) { 534 smartAddText(td, formatPhrase(RenderingContext.CODESYSTEM_DEPRECATED)); 535 hasExtensions = true; 536 if (ToolingExtensions.hasExtension(c, ToolingExtensions.EXT_REPLACED_BY)) { 537 Coding cc = (Coding) ToolingExtensions.getExtension(c, ToolingExtensions.EXT_REPLACED_BY).getValue(); 538 td.tx(" "+ context.formatPhrase(RenderingContext.CODE_SYS_REPLACED_BY) + " "); 539 String url = getCodingReference(cc, system); 540 if (url != null) { 541 td.ah(context.prefixLocalHref(url)).addText(cc.getCode()); 542 td.tx(": "+cc.getDisplay()+")"); 543 } else 544 td.addText(cc.getCode()+" '"+cc.getDisplay()+"' in "+cc.getSystem()+")"); 545 } else { 546 Extension ext = c.getExtensionByUrl(ToolingExtensions.EXT_STANDARDS_STATUS); 547 if (ext != null) { 548 ext = ext.getValue().getExtensionByUrl(ToolingExtensions.EXT_STANDARDS_STATUS_REASON); 549 if (ext != null) { 550 addMarkdown(td, ext.getValue().primitiveValue()); 551 } 552 } 553 } 554 } 555 } 556 if (comment) { 557 td = tr.td(); 558 Extension ext = c.getExtensionByUrl(ToolingExtensions.EXT_CS_COMMENT); 559 if (ext != null && ext.hasValue() && ext.getValue().primitiveValue() != null) { 560 hasExtensions = true; 561 StringType defn = context.getTranslatedElement((PrimitiveType<?>) ext.getValue()); 562 if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE ||!(ToolingExtensions.hasLanguageTranslations(ext.getValue()))) { 563 td.addText(defn.asStringValue()); 564 } else { 565 List<Translateable> list = new ArrayList<>(); 566 list.add(new Translateable(cs.getLanguage(), defn)); 567 for (Extension ex : defn.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) { 568 hasExtensions = true; 569 list.add(new Translateable(ex.getExtensionString("lang"), ex.getExtensionByUrl("content").getValueStringType())); 570 } 571 boolean first = true; 572 for (Translateable ti : list) { 573 if (first) { 574 first = false; 575 } else { 576 td.br(); 577 } 578 579 if (ti.lang != null) { 580 td.addText(ti.lang + ": "); 581 } 582 renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue()); 583 } 584 } 585 } 586 } 587 if (version) { 588 td = tr.td(); 589 if (c.hasUserData(UserDataNames.tx_cs_version_notes)) { // todo: this is never set 590 td.addText(c.getUserString(UserDataNames.tx_cs_version_notes)); 591 } 592 } 593 594 if (properties != null) { 595 for (PropertyComponent pc : properties) { 596 td = tr.td(); 597 boolean first = true; 598 List<ConceptPropertyComponent> pcvl = CodeSystemUtilities.getPropertyValues(c, pc.getCode()); 599 for (ConceptPropertyComponent pcv : pcvl) { 600 if (pcv.hasValue()) { 601 if (first) first = false; else td.addText(", "); 602 if (pcv.hasValueCoding()) { 603 td.addText(pcv.getValueCoding().getCode()); 604 } else { 605 String pv = pcv.getValue().primitiveValue(); 606 if (pcv.hasValueStringType() && Utilities.isAbsoluteUrl(pv)) { 607 CanonicalResource cr = (CanonicalResource) context.getContext().fetchResource(Resource.class, pv); 608 if (cr != null) { 609 if (cr.hasWebPath()) { 610 td.ah(context.prefixLocalHref(cr.getWebPath()), cr.getVersionedUrl()).tx(cr.present()); 611 } else { 612 td.ah(cr.getVersionedUrl(), cr.getVersionedUrl()).tx(cr.present()); 613 } 614 } else if (Utilities.isAbsoluteUrlLinkable(pv) && !isInKnownUrlSpace(pv)) { 615 td.ah(context.prefixLocalHref(pv)).tx(pv); 616 } else { 617 td.code(pv); 618 } 619 } else if ("parent".equals(pcv.getCode())) { 620 td.ah(context.prefixLocalHref("#"+cs.getId()+"-"+Utilities.nmtokenize(pv))).addText(pv); 621 } else { 622 td.addText(pv); 623 } 624 } 625 } 626 } 627 } 628 } 629 630 if (langs != null) { 631 for (String lang : langs) { 632 td = tr.td().tx(getDisplay(lang, c)); 633 } 634 } 635 for (UsedConceptMap m : maps) { 636 td = tr.td(); 637 List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap()); 638 boolean first = true; 639 for (TargetElementComponentWrapper mapping : mappings) { 640 if (!first) 641 td.br(); 642 first = false; 643 XhtmlNode span = td.span(null, mapping.comp.hasRelationship() ? mapping.comp.getRelationship().toCode() : ""); 644 span.addText(getCharForRelationship(mapping.comp)); 645 a = td.ah(context.prefixLocalHref(getContext().getLink(KnownLinkType.SPEC, true)+m.getLink()+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode()))); 646 a.addText(mapping.comp.getCode()); 647 if (!Utilities.noString(mapping.comp.getComment())) 648 td.i().tx("("+mapping.comp.getComment()+")"); 649 } 650 } 651 List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c); 652 for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) { 653 addDefineRowToTable(status, t, cc, level+1, hasHierarchy, hasDisplay, hasDefinitions, comment, version, deprecated, maps, system, cs, properties, csNav, langs, isSupplement); 654 } 655 for (ConceptDefinitionComponent cc : ocl) { 656 tr = t.tr(); 657 td = tr.td(); 658 td.addText(Integer.toString(level+2)); 659 td = tr.td(); 660 String s = Utilities.padLeft("", '\u00A0', (level+1)*2); 661 td.addText(s); 662 td.style("white-space:nowrap"); 663 a = td.ah(context.prefixLocalHref("#"+cs.getId()+"-" + Utilities.nmtokenize(cc.getCode()))); 664 a.addText(cc.getCode()); 665 if (hasDisplay) { 666 td = tr.td(); 667 hasExtensions = renderDisplayName(cc, cs, td, langs) || hasExtensions; 668 } 669 int w = 1 + (deprecated ? 1 : 0) + (comment ? 1 : 0) + (version ? 1 : 0) + maps.size(); 670 if (properties != null) { 671 w = w + properties.size(); 672 } 673 td = tr.td().colspan(Integer.toString(w)); 674 } 675 if (context.isCopyButton()) { 676 td = tr.td(); 677 clipboard(td, "icon_clipboard_x.png", "XML", "<system value=\""+Utilities.escapeXml(cs.getUrl())+"\">\n"+(cs.getVersionNeeded() ? "<version value=\""+Utilities.escapeXml(cs.getVersion())+"\">\n" : "")+"<code value=\""+Utilities.escapeXml(c.getCode())+"\">\n<display value=\""+Utilities.escapeXml(c.getDisplay())+"\">\n"); 678 td.nbsp(); 679 clipboard(td, "icon_clipboard_j.png", "JSON", "\"system\" : \""+Utilities.escapeXml(cs.getUrl())+"\",\n"+(cs.getVersionNeeded() ? "\"version\" : \""+Utilities.escapeXml(cs.getVersion())+"\",\n" : "")+"\"code\" : \""+Utilities.escapeXml(c.getCode())+"\",\n\"display\" : \""+Utilities.escapeXml(c.getDisplay())+"\"\n"); 680 } 681 } 682 683 684 private String getDisplay(String lang, ConceptDefinitionComponent c) { 685 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 686 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(lang)) { 687 return cd.getValue(); 688 } 689 } 690 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 691 if (cd.hasLanguage() && cd.getLanguage().equals(lang)) { 692 return cd.getValue(); 693 } 694 } 695 return null; 696 } 697 698 private boolean hasMarkdownInDefinitions(CodeSystem cs) { 699 if (doMarkdown == null) { 700 if (cs.hasUserData(UserDataNames.CS_MARKDOWN_FLAG)) { 701 doMarkdown = (Boolean) cs.getUserData(UserDataNames.CS_MARKDOWN_FLAG); 702 } else { 703 if (cs.hasExtension("http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown")) { 704 doMarkdown = ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown"); 705 } else { 706 doMarkdown = CodeSystemUtilities.hasMarkdownInDefinitions(cs, context.getMarkdown()); 707 } 708 cs.setUserData(UserDataNames.CS_MARKDOWN_FLAG, doMarkdown); 709 } 710 } 711 return doMarkdown; 712 } 713 714 715 public boolean renderDisplayName(ConceptDefinitionComponent c, CodeSystem cs, XhtmlNode td, List<String> langs) { 716 boolean hasExtensions = false; 717 if (c.hasDisplayElement()) { 718 StringType disp = c.getDisplayElement(); 719 List<Translateable> list = new ArrayList<>(); 720 list.add(new Translateable(cs.getLanguage(), disp)); 721 for (Extension ext : disp.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) { 722 if (!langs.contains(ext.getExtensionString("lang"))) { 723 hasExtensions = true; 724 list.add(new Translateable(ext.getExtensionString("lang"), ext.getExtensionByUrl("content").getValueStringType())); 725 } 726 } 727 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 728 if (cd.hasLanguage() && (langs == null || !langs.contains(cd.getLanguage())) && (c.getDefinition() == null || !c.getDefinition().equalsIgnoreCase(cd.getValue()))) { 729 list.add(new Translateable(cd.getLanguage(), cd.getValueElement())); 730 } 731 } 732 733 if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE || list.size() <= 1) { 734 renderStatus(disp, td).addText(disp.asStringValue()); 735 } else { 736 boolean first = true; 737 for (Translateable ti : list) { 738 if (first) { 739 first = false; 740 } else { 741 td.br(); 742 } 743 744 if (ti.lang != null) { 745 td.addText(ti.lang + ": "); 746 } 747 renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue()); 748 } 749 750 } 751 } 752 return hasExtensions; 753 } 754 755 private String getCodingReference(Coding cc, String system) { 756 if (cc.getSystem().equals(system)) 757 return "#"+cc.getCode(); 758 if (cc.getSystem().equals("http://snomed.info/sct")) 759 return "http://snomed.info/sct/"+cc.getCode(); 760 if (cc.getSystem().equals("http://loinc.org")) 761 return LoincLinker.getLinkForCode(cc.getCode()); 762 return null; 763 } 764 765 766 private void addLanguageRow(ConceptDefinitionComponent c, XhtmlNode t, List<String> langs) { 767 XhtmlNode tr = t.tr(); 768 tr.td().addText(c.getCode()); 769 for (String lang : langs) { 770 ConceptDefinitionDesignationComponent d = null; 771 for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) { 772 if (designation.hasLanguage()) { 773 if (lang.equals(designation.getLanguage())) 774 d = designation; 775 } 776 } 777 tr.td().addText(d == null ? "" : d.getValue()); 778 } 779 } 780 781 782 @Override 783 protected void genSummaryTableContent(RenderingStatus status, XhtmlNode tbl, CanonicalResource cr) throws IOException { 784 super.genSummaryTableContent(status, tbl, cr); 785 786 CodeSystem cs = (CodeSystem) cr; 787 XhtmlNode tr; 788 if (cs.hasContent()) { 789 tr = tbl.tr(); 790 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_CONTENT)+":"); 791 XhtmlNode td = tr.td(); 792 td.tx((cs.getContent().getDisplay())+": "+describeContent(cs.getContent(), cs)); 793 if (cs.getContent() == CodeSystemContentMode.SUPPLEMENT) { 794 td.tx(" "); 795 CodeSystem tgt = context.getContext().fetchCodeSystem(cs.getSupplements()); 796 if (tgt != null) { 797 td.ah(tgt.getWebPath()).tx(tgt.present()); 798 } else { 799 td.code().tx(cs.getSupplements()); 800 } 801 } 802 } 803 804 if (CodeSystemUtilities.hasOID(cs)) { 805 tr = tbl.tr(); 806 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_OID)+":"); 807 tr.td().tx(context.formatPhrase(RenderingContext.CODE_SYS_FOR_OID, CodeSystemUtilities.getOID(cs))); 808 } 809 810 if (cs.hasValueSet()) { 811 tr = tbl.tr(); 812 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUESET)+":"); 813 ValueSet vs = context.getContext().findTxResource(ValueSet.class, cs.getValueSet()); 814 if (vs == null) { 815 tr.td().tx(context.formatPhrase(RenderingContext.CODE_SYS_THE_VALUE_SET, cs.getValueSet())+")"); 816 } else { 817 tr.td().ah(vs.getWebPath()).tx(context.formatPhrase(RenderingContext.CODE_SYS_THE_VALUE_SET, cs.getValueSet())+")"); 818 } 819 } 820 } 821 822 private String describeContent(CodeSystemContentMode content, CodeSystem cs) { 823 switch (content) { 824 case COMPLETE: return (context.formatPhrase(RenderingContext.CODE_SYS_COMPLETE)); 825 case NOTPRESENT: return (context.formatPhrase(RenderingContext.CODE_SYS_NOTPRESENT)); 826 case EXAMPLE: return (context.formatPhrase(RenderingContext.CODE_SYS_EXAMPLE)); 827 case FRAGMENT: return (context.formatPhrase(RenderingContext.CODE_SYS_FRAGMENT)); 828 case SUPPLEMENT: return (context.formatPhrase(RenderingContext.CODE_SYS_SUPPLEMENT)); 829 default: 830 return "?? illegal content status value "+(content == null ? "(null)" : content.toCode()); 831 } 832 } 833 834 835}