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