
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 if (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 return code == null ? false : CodeSystemUtilities.hasCode(cs, code); 400 } 401 return false; 402 } 403 404 405 private int countConcepts(List<ConceptDefinitionComponent> list) { 406 int count = list.size(); 407 for (ConceptDefinitionComponent c : list) 408 if (c.hasConcept()) 409 count = count + countConcepts(c.getConcept()); 410 return count; 411 } 412 413 private boolean conceptsHaveComments(ConceptDefinitionComponent c) { 414 if (ToolingExtensions.hasCSComment(c)) 415 return true; 416 for (ConceptDefinitionComponent g : c.getConcept()) 417 if (conceptsHaveComments(g)) 418 return true; 419 return false; 420 } 421 422 private boolean conceptsHaveDisplay(ConceptDefinitionComponent c) { 423 if (c.hasDisplay() && !c.getDisplay().equals(c.getCode())) 424 return true; 425 for (ConceptDefinitionComponent g : c.getConcept()) 426 if (conceptsHaveDisplay(g)) 427 return true; 428 return false; 429 } 430 431 private boolean conceptsHaveVersion(ConceptDefinitionComponent c) { 432 if (c.hasUserData(UserDataNames.tx_cs_version_notes)) 433 return true; 434 for (ConceptDefinitionComponent g : c.getConcept()) 435 if (conceptsHaveVersion(g)) 436 return true; 437 return false; 438 } 439 440 private boolean conceptsHaveDeprecated(CodeSystem cs, ConceptDefinitionComponent c, boolean ignoreStatus) { 441 if (CodeSystemUtilities.isDeprecated(cs, c, ignoreStatus)) 442 return true; 443 for (ConceptDefinitionComponent g : c.getConcept()) 444 if (conceptsHaveDeprecated(cs, g, ignoreStatus)) 445 return true; 446 return false; 447 } 448 449 450 451 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 { 452 boolean hasExtensions = false; 453 XhtmlNode tr = t.tr(); 454 boolean notCurrent = CodeSystemUtilities.isNotCurrent(cs, c); 455 if (notCurrent) { 456 tr.setAttribute("style", "background-color: #ffeeee"); 457 } 458 459 XhtmlNode td = renderStatusRow(c, t, tr); 460 if (hasHierarchy) { 461 td.addText(Integer.toString(level+1)); 462 td = tr.td(); 463 String s = Utilities.padLeft("", '\u00A0', level*2); 464 td.addText(s); 465 } 466 String link = isSupplement ? getLinkForCode(cs.getSupplements(), null, c.getCode()) : null; 467 if (link != null) { 468 td.ah(context.prefixLocalHref(link)).style( "white-space:nowrap").addText(c.getCode()); 469 } else { 470 td.style("white-space:nowrap").addText(c.getCode()); 471 } 472 XhtmlNode a; 473 if (c.hasCodeElement()) { 474 td.an(context.prefixAnchor(cs.getId()+"-" + Utilities.nmtokenize(c.getCode()))); 475 } 476 477 if (hasDisplay) { 478 td = tr.td(); 479 hasExtensions = renderDisplayName(c, cs, td, langs) || hasExtensions; 480 } 481 if (hasDefinitions) { 482 td = tr.td(); 483 if (c != null &&c.hasDefinitionElement()) { 484 // translations of the definition might come from either the translation extension, or from the designations 485 StringType defn = context.getTranslatedElement(c.getDefinitionElement()); 486 boolean sl = false; 487 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 488 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) { 489 sl = true; 490 } 491 } 492 493 if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE || !(sl || ToolingExtensions.hasLanguageTranslations(defn))) { 494 if (hasMarkdownInDefinitions(cs)) { 495 addMarkdown(renderStatusDiv(defn, td), defn.asStringValue()); 496 } else { 497 renderStatus(defn, td).addText(defn.asStringValue()); 498 } 499 } else { 500 List<Translateable> list = new ArrayList<>(); 501 list.add(new Translateable(cs.getLanguage(), defn)); 502 for (Extension ext : defn.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) { 503 hasExtensions = true; 504 list.add(new Translateable(ext.getExtensionString("lang"), ext.getExtensionByUrl("content").getValueStringType())); 505 } 506 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 507 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) { 508 list.add(new Translateable(cd.getLanguage(), cd.getValueElement())); 509 } 510 } 511 boolean first = true; 512 for (Translateable ti : list) { 513 if (first) { 514 first = false; 515 } else { 516 td.br(); 517 } 518 519 if (ti.lang != null) { 520 td.addText(ti.lang + ": "); 521 } 522 if (hasMarkdownInDefinitions(cs)) { 523 addMarkdown(renderStatusDiv(ti.getValue(), td), ti.getValue().asStringValue()); 524 } else { 525 renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue()); 526 } 527 } 528 } 529 } 530 } 531 if (deprecated) { 532 td = tr.td(); 533 Boolean b = CodeSystemUtilities.isDeprecated(cs, c, false); 534 if (b != null && b) { 535 smartAddText(td, formatPhrase(RenderingContext.CODESYSTEM_DEPRECATED)); 536 hasExtensions = true; 537 if (ToolingExtensions.hasExtension(c, ToolingExtensions.EXT_REPLACED_BY)) { 538 Coding cc = (Coding) ToolingExtensions.getExtension(c, ToolingExtensions.EXT_REPLACED_BY).getValue(); 539 td.tx(" "+ context.formatPhrase(RenderingContext.CODE_SYS_REPLACED_BY) + " "); 540 String url = getCodingReference(cc, system); 541 if (url != null) { 542 td.ah(context.prefixLocalHref(url)).addText(cc.getCode()); 543 td.tx(": "+cc.getDisplay()+")"); 544 } else 545 td.addText(cc.getCode()+" '"+cc.getDisplay()+"' in "+cc.getSystem()+")"); 546 } else { 547 Extension ext = c.getExtensionByUrl(ToolingExtensions.EXT_STANDARDS_STATUS); 548 if (ext != null) { 549 ext = ext.getValue().getExtensionByUrl(ToolingExtensions.EXT_STANDARDS_STATUS_REASON); 550 if (ext != null) { 551 addMarkdown(td, ext.getValue().primitiveValue()); 552 } 553 } 554 } 555 } 556 } 557 if (comment) { 558 td = tr.td(); 559 Extension ext = c.getExtensionByUrl(ToolingExtensions.EXT_CS_COMMENT); 560 if (ext != null && ext.hasValue() && ext.getValue().primitiveValue() != null) { 561 hasExtensions = true; 562 StringType defn = context.getTranslatedElement((PrimitiveType<?>) ext.getValue()); 563 if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE ||!(ToolingExtensions.hasLanguageTranslations(ext.getValue()))) { 564 td.addText(defn.asStringValue()); 565 } else { 566 List<Translateable> list = new ArrayList<>(); 567 list.add(new Translateable(cs.getLanguage(), defn)); 568 for (Extension ex : defn.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) { 569 hasExtensions = true; 570 list.add(new Translateable(ex.getExtensionString("lang"), ex.getExtensionByUrl("content").getValueStringType())); 571 } 572 boolean first = true; 573 for (Translateable ti : list) { 574 if (first) { 575 first = false; 576 } else { 577 td.br(); 578 } 579 580 if (ti.lang != null) { 581 td.addText(ti.lang + ": "); 582 } 583 renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue()); 584 } 585 } 586 } 587 } 588 if (version) { 589 td = tr.td(); 590 if (c.hasUserData(UserDataNames.tx_cs_version_notes)) { // todo: this is never set 591 td.addText(c.getUserString(UserDataNames.tx_cs_version_notes)); 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 if (pcv.hasValueStringType() && Utilities.isAbsoluteUrl(pcv.getValue().primitiveValue())) { 605 CanonicalResource cr = (CanonicalResource) context.getContext().fetchResource(Resource.class, pcv.getValue().primitiveValue()); 606 if (cr != null) { 607 td.ah(context.prefixLocalHref(cr.getWebPath()), cr.getVersionedUrl()).tx(cr.present()); 608 } else if (Utilities.isAbsoluteUrlLinkable(pcv.getValue().primitiveValue())) { 609 td.ah(context.prefixLocalHref(pcv.getValue().primitiveValue())).tx(pcv.getValue().primitiveValue()); 610 } else { 611 td.code(pcv.getValue().primitiveValue()); 612 } 613 } else if ("parent".equals(pcv.getCode())) { 614 td.ah(context.prefixLocalHref("#"+cs.getId()+"-"+Utilities.nmtokenize(pcv.getValue().primitiveValue()))).addText(pcv.getValue().primitiveValue()); 615 } else { 616 td.addText(pcv.getValue().primitiveValue()); 617 } 618 } 619 } 620 } 621 } 622 623 if (langs != null) { 624 for (String lang : langs) { 625 td = tr.td().tx(getDisplay(lang, c)); 626 } 627 } 628 for (UsedConceptMap m : maps) { 629 td = tr.td(); 630 List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap()); 631 boolean first = true; 632 for (TargetElementComponentWrapper mapping : mappings) { 633 if (!first) 634 td.br(); 635 first = false; 636 XhtmlNode span = td.span(null, mapping.comp.hasRelationship() ? mapping.comp.getRelationship().toCode() : ""); 637 span.addText(getCharForRelationship(mapping.comp)); 638 a = td.ah(context.prefixLocalHref(getContext().getLink(KnownLinkType.SPEC)+m.getLink()+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode()))); 639 a.addText(mapping.comp.getCode()); 640 if (!Utilities.noString(mapping.comp.getComment())) 641 td.i().tx("("+mapping.comp.getComment()+")"); 642 } 643 } 644 List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c); 645 for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) { 646 addDefineRowToTable(status, t, cc, level+1, hasHierarchy, hasDisplay, hasDefinitions, comment, version, deprecated, maps, system, cs, properties, csNav, langs, isSupplement); 647 } 648 for (ConceptDefinitionComponent cc : ocl) { 649 tr = t.tr(); 650 td = tr.td(); 651 td.addText(Integer.toString(level+2)); 652 td = tr.td(); 653 String s = Utilities.padLeft("", '\u00A0', (level+1)*2); 654 td.addText(s); 655 td.style("white-space:nowrap"); 656 a = td.ah(context.prefixLocalHref("#"+cs.getId()+"-" + Utilities.nmtokenize(cc.getCode()))); 657 a.addText(cc.getCode()); 658 if (hasDisplay) { 659 td = tr.td(); 660 hasExtensions = renderDisplayName(cc, cs, td, langs) || hasExtensions; 661 } 662 int w = 1 + (deprecated ? 1 : 0) + (comment ? 1 : 0) + (version ? 1 : 0) + maps.size(); 663 if (properties != null) { 664 w = w + properties.size(); 665 } 666 td = tr.td().colspan(Integer.toString(w)); 667 } 668 if (context.isCopyButton()) { 669 td = tr.td(); 670 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"); 671 td.nbsp(); 672 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"); 673 } 674 } 675 676 private String getDisplay(String lang, ConceptDefinitionComponent c) { 677 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 678 if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(lang)) { 679 return cd.getValue(); 680 } 681 } 682 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 683 if (cd.hasLanguage() && cd.getLanguage().equals(lang)) { 684 return cd.getValue(); 685 } 686 } 687 return null; 688 } 689 690 private boolean hasMarkdownInDefinitions(CodeSystem cs) { 691 if (doMarkdown == null) { 692 if (cs.hasExtension("http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown")) { 693 doMarkdown = ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown"); 694 } else { 695 doMarkdown = CodeSystemUtilities.hasMarkdownInDefinitions(cs, context.getMarkdown()); 696 } 697 } 698 return doMarkdown; 699 } 700 701 702 public boolean renderDisplayName(ConceptDefinitionComponent c, CodeSystem cs, XhtmlNode td, List<String> langs) { 703 boolean hasExtensions = false; 704 if (c.hasDisplayElement()) { 705 StringType disp = c.getDisplayElement(); 706 List<Translateable> list = new ArrayList<>(); 707 list.add(new Translateable(cs.getLanguage(), disp)); 708 for (Extension ext : disp.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) { 709 if (!langs.contains(ext.getExtensionString("lang"))) { 710 hasExtensions = true; 711 list.add(new Translateable(ext.getExtensionString("lang"), ext.getExtensionByUrl("content").getValueStringType())); 712 } 713 } 714 for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { 715 if (cd.hasLanguage() && (langs == null || !langs.contains(cd.getLanguage())) && (c.getDefinition() == null || !c.getDefinition().equalsIgnoreCase(cd.getValue()))) { 716 list.add(new Translateable(cd.getLanguage(), cd.getValueElement())); 717 } 718 } 719 720 if (getContext().getMultiLanguagePolicy() == MultiLanguagePolicy.NONE || list.size() <= 1) { 721 renderStatus(disp, td).addText(disp.asStringValue()); 722 } else { 723 boolean first = true; 724 for (Translateable ti : list) { 725 if (first) { 726 first = false; 727 } else { 728 td.br(); 729 } 730 731 if (ti.lang != null) { 732 td.addText(ti.lang + ": "); 733 } 734 renderStatus(ti.getValue(), td).addText(ti.getValue().asStringValue()); 735 } 736 737 } 738 } 739 return hasExtensions; 740 } 741 742 private String getCodingReference(Coding cc, String system) { 743 if (cc.getSystem().equals(system)) 744 return "#"+cc.getCode(); 745 if (cc.getSystem().equals("http://snomed.info/sct")) 746 return "http://snomed.info/sct/"+cc.getCode(); 747 if (cc.getSystem().equals("http://loinc.org")) 748 return LoincLinker.getLinkForCode(cc.getCode()); 749 return null; 750 } 751 752 753 private void addLanguageRow(ConceptDefinitionComponent c, XhtmlNode t, List<String> langs) { 754 XhtmlNode tr = t.tr(); 755 tr.td().addText(c.getCode()); 756 for (String lang : langs) { 757 ConceptDefinitionDesignationComponent d = null; 758 for (ConceptDefinitionDesignationComponent designation : c.getDesignation()) { 759 if (designation.hasLanguage()) { 760 if (lang.equals(designation.getLanguage())) 761 d = designation; 762 } 763 } 764 tr.td().addText(d == null ? "" : d.getValue()); 765 } 766 } 767 768 769 @Override 770 protected void genSummaryTableContent(RenderingStatus status, XhtmlNode tbl, CanonicalResource cr) throws IOException { 771 super.genSummaryTableContent(status, tbl, cr); 772 773 CodeSystem cs = (CodeSystem) cr; 774 XhtmlNode tr; 775 if (cs.hasContent()) { 776 tr = tbl.tr(); 777 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_CONTENT)+":"); 778 XhtmlNode td = tr.td(); 779 td.tx((cs.getContent().getDisplay())+": "+describeContent(cs.getContent(), cs)); 780 if (cs.getContent() == CodeSystemContentMode.SUPPLEMENT) { 781 td.tx(" "); 782 CodeSystem tgt = context.getContext().fetchCodeSystem(cs.getSupplements()); 783 if (tgt != null) { 784 td.ah(tgt.getWebPath()).tx(tgt.present()); 785 } else { 786 td.code().tx(cs.getSupplements()); 787 } 788 } 789 } 790 791 if (CodeSystemUtilities.hasOID(cs)) { 792 tr = tbl.tr(); 793 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_OID)+":"); 794 tr.td().tx(context.formatPhrase(RenderingContext.CODE_SYS_FOR_OID, CodeSystemUtilities.getOID(cs))); 795 } 796 797 if (cs.hasValueSet()) { 798 tr = tbl.tr(); 799 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUESET)+":"); 800 ValueSet vs = context.getContext().findTxResource(ValueSet.class, cs.getValueSet()); 801 if (vs == null) { 802 tr.td().tx(context.formatPhrase(RenderingContext.CODE_SYS_THE_VALUE_SET, cs.getValueSet())+")"); 803 } else { 804 tr.td().ah(vs.getWebPath()).tx(context.formatPhrase(RenderingContext.CODE_SYS_THE_VALUE_SET, cs.getValueSet())+")"); 805 } 806 } 807 } 808 809 private String describeContent(CodeSystemContentMode content, CodeSystem cs) { 810 switch (content) { 811 case COMPLETE: return (context.formatPhrase(RenderingContext.CODE_SYS_COMPLETE)); 812 case NOTPRESENT: return (context.formatPhrase(RenderingContext.CODE_SYS_NOTPRESENT)); 813 case EXAMPLE: return (context.formatPhrase(RenderingContext.CODE_SYS_EXAMPLE)); 814 case FRAGMENT: return (context.formatPhrase(RenderingContext.CODE_SYS_FRAGMENT)); 815 case SUPPLEMENT: return (context.formatPhrase(RenderingContext.CODE_SYS_SUPPLEMENT)); 816 default: 817 return "?? illegal content status value "+(content == null ? "(null)" : content.toCode()); 818 } 819 } 820 821 822}