
001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.text.ParseException; 006import java.text.SimpleDateFormat; 007import java.util.ArrayList; 008import java.util.Collections; 009import java.util.Date; 010import java.util.HashMap; 011import java.util.HashSet; 012import java.util.List; 013import java.util.Map; 014import java.util.Set; 015 016import org.hl7.fhir.exceptions.DefinitionException; 017import org.hl7.fhir.exceptions.FHIRException; 018import org.hl7.fhir.exceptions.FHIRFormatError; 019import org.hl7.fhir.exceptions.TerminologyServiceException; 020import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation; 021import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 022import org.hl7.fhir.r5.extensions.ExtensionUtilities; 023import org.hl7.fhir.r5.model.Base; 024import org.hl7.fhir.r5.model.CanonicalResource; 025import org.hl7.fhir.r5.model.CodeSystem; 026import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 027import org.hl7.fhir.r5.model.Coding; 028import org.hl7.fhir.r5.model.ConceptMap; 029import org.hl7.fhir.r5.model.DataType; 030import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 031import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 032import org.hl7.fhir.r5.model.Expression; 033import org.hl7.fhir.r5.model.Extension; 034import org.hl7.fhir.r5.model.ExtensionHelper; 035import org.hl7.fhir.r5.model.PrimitiveType; 036import org.hl7.fhir.r5.model.Resource; 037import org.hl7.fhir.r5.model.StringType; 038import org.hl7.fhir.r5.model.UriType; 039import org.hl7.fhir.r5.model.ValueSet; 040import org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent; 041import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 042import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 043import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 044import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 045import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 046import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 047import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 048import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent; 049import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; 050import org.hl7.fhir.r5.renderers.utils.RenderingContext; 051import org.hl7.fhir.r5.renderers.utils.RenderingContext.DesignationMode; 052import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 053import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 054import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 055import org.hl7.fhir.r5.terminologies.ValueSetUtilities; 056import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 057import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest; 058import org.hl7.fhir.r5.terminologies.utilities.SnomedUtilities; 059import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 060import org.hl7.fhir.r5.utils.EOperationOutcome; 061 062import org.hl7.fhir.r5.utils.UserDataNames; 063import org.hl7.fhir.utilities.LoincLinker; 064import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 065import org.hl7.fhir.utilities.Utilities; 066import org.hl7.fhir.utilities.i18n.RenderingI18nContext; 067import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 068import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 069import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 070import org.hl7.fhir.utilities.xhtml.XhtmlNode; 071 072import com.google.common.collect.HashMultimap; 073import com.google.common.collect.Multimap; 074 075@MarkedToMoveToAdjunctPackage 076public class ValueSetRenderer extends TerminologyRenderer { 077 078 public ValueSetRenderer(RenderingContext context) { 079 super(context); 080 } 081 082 @Override 083 public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 084 if (!r.isDirect()) { 085 // the intention is to change this in the future 086 x.para().tx("ValueSetRenderer only renders native resources directly"); 087 } else { 088 renderResourceTechDetails(r, x); 089 ValueSet vs = (ValueSet) r.getBase(); 090 genSummaryTable(status, x, vs); 091 List<UsedConceptMap> maps = findReleventMaps(vs); 092 093 if (context.isShowSummaryTable()) { 094 XhtmlNode h = x.h2(); 095 h.addText(vs.hasTitle() ? vs.getTitle() : vs.getName()); 096 addMarkdown(x, vs.getDescription()); 097 if (vs.hasCopyright()) 098 generateCopyright(x, r); 099 } 100 if (vs.hasExtension(ExtensionDefinitions.EXT_VS_CS_SUPPL_NEEDED)) { 101 List<Extension> exts = vs.getExtensionsByUrl(ExtensionDefinitions.EXT_VS_CS_SUPPL_NEEDED); 102 var p = x.para(); 103 p.tx(context.formatPhrasePlural(exts.size(), RenderingI18nContext.VALUE_SET_NEEDS_SUPPL)); 104 p.tx(" "); 105 for (int i = 0; i < exts.size(); i++) { 106 if (i > 0) { 107 if (i == exts.size() - 1) { 108 p.tx(" and "); 109 } else { 110 p.tx(", "); 111 } 112 } 113 String u = exts.get(i).getValue().primitiveValue(); 114 CodeSystem cs = context.getContext().fetchResource(CodeSystem.class, u); 115 if (cs == null) { 116 p.code().tx(u); 117 } else if (!cs.hasWebPath()) { 118 p.ah(u).tx(cs.present()); 119 } else { 120 p.ah(cs.getWebPath()).tx(cs.present()); 121 } 122 } 123 p.tx("."); 124 } 125 if (vs.hasExtension(ExtensionDefinitions.EXT_VALUESET_PARAMETER)) { 126 x.para().b().tx("This ValueSet has parameters"); 127 XhtmlNode tbl = x.table("grid"); 128 XhtmlNode tr = tbl.tr(); 129 tr.th().tx("Name"); 130 tr.th().tx("Documentation"); 131 for (Extension ext : vs.getExtensionsByUrl(ExtensionDefinitions.EXT_VALUESET_PARAMETER)) { 132 tr = tbl.tr(); 133 tr.td().tx(ext.getExtensionString("name")); 134 tr.td().markdown(ext.getExtensionString("documentation"), "parameter.documentation"); 135 } 136 } 137 if (vs.hasExpansion()) { 138 // for now, we just accept an expansion if there is one 139 generateExpansion(status, r, x, vs, false, maps); 140 } else { 141 generateComposition(status, r, x, vs, false, maps); 142 } 143 } 144 } 145 146 147 @Override 148 public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 149 return canonicalTitle(r); 150 } 151 152 private static final int MAX_DESIGNATIONS_IN_LINE = 5; 153 154 private static final int MAX_BATCH_VALIDATION_SIZE = 1000; 155 156 private List<ConceptMapRenderInstructions> renderingMaps = new ArrayList<ConceptMapRenderInstructions>(); 157 158 private Map<String, String> oidMap; 159 160 public void render(RenderingStatus status, XhtmlNode x, ValueSet vs, boolean header) throws FHIRFormatError, DefinitionException, IOException { 161 162 } 163 164 public void describe(XhtmlNode x, ValueSet vs) { 165 x.tx(display(vs)); 166 } 167 168 public String display(ValueSet vs) { 169 return vs.present(); 170 } 171 172 173 private List<UsedConceptMap> findReleventMaps(ValueSet vs) throws FHIRException { 174 List<UsedConceptMap> res = new ArrayList<UsedConceptMap>(); 175 for (ConceptMap cm : getContext().getWorker().fetchResourcesByType(ConceptMap.class)) { 176 if (isSource(vs, cm.getSourceScope())) { 177 ConceptMapRenderInstructions re = findByTarget(cm.getTargetScope()); 178 if (re == null) { 179 re = new ConceptMapRenderInstructions(cm.present(), cm.getUrl(), false); 180 } 181 if (re != null) { 182 ValueSet vst = cm.hasTargetScope() ? getContext().getWorker().findTxResource(ValueSet.class, cm.hasTargetScopeCanonicalType() ? cm.getTargetScopeCanonicalType().getValue() : cm.getTargetScopeUriType().asStringValue(), cm) : null; 183 res.add(new UsedConceptMap(re, vst == null ? cm.getWebPath() : vst.getWebPath(), cm)); 184 } 185 } 186 } 187 return res; 188 189// @Override 190// public List<ConceptMap> findMapsForSource(String url) throws FHIRException { 191// synchronized (lock) { 192// List<ConceptMap> res = new ArrayList<ConceptMap>(); 193// for (ConceptMap map : maps.getList()) { 194// if (((Reference) map.getSourceScope()).getReference().equals(url)) { 195// res.add(map); 196// } 197// } 198// return res; 199// } 200// } 201 202// Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>(); 203// for (ConceptMap a : context.getWorker().findMapsForSource(vs.getUrl())) { 204// String url = ""; 205// ValueSet vsr = context.getWorker().findTxResource(ValueSet.class, ((Reference) a.getTarget()).getReference()); 206// if (vsr != null) 207// url = (String) vsr.getUserData(UserDataNames.filename); 208// mymaps.put(a, url); 209// } 210// Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>(); 211// for (ConceptMap a : context.getWorker().findMapsForSource(cs.getValueSet())) { 212// String url = ""; 213// ValueSet vsr = context.getWorker().fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference()); 214// if (vsr != null) 215// url = (String) vsr.getUserData(UserDataNames.filename); 216// mymaps.put(a, url); 217// } 218 // also, look in the contained resources for a concept map 219// for (Resource r : cs.getContained()) { 220// if (r instanceof ConceptMap) { 221// ConceptMap cm = (ConceptMap) r; 222// if (((Reference) cm.getSource()).getReference().equals(cs.getValueSet())) { 223// String url = ""; 224// ValueSet vsr = context.getWorker().findTxResource(ValueSet.class, ((Reference) cm.getTarget()).getReference()); 225// if (vsr != null) 226// url = (String) vsr.getUserData(UserDataNames.filename); 227// mymaps.put(cm, url); 228// } 229// } 230// } 231 } 232 233 private boolean isSource(ValueSet vs, DataType source) { 234 return vs.hasUrl() && source != null && vs.getUrl().equals(source.primitiveValue()); 235 } 236 237 private void generateExpansion(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException { 238 List<String> langs = new ArrayList<String>(); 239 Map<String, String> designations = new HashMap<>(); // map of url = description, where url is the designation code. Designations that are for languages won't make it into this list 240 Map<String, String> properties = new HashMap<>(); // map of url = description, where url is the designation code. Designations that are for languages won't make it into this list 241 242 if (header) { 243 XhtmlNode h = x.addTag(getHeader()); 244 h.tx(context.formatPhrase(RenderingContext.VALUE_SET_CONT)); 245 if (IsNotFixedExpansion(vs)) 246 addMarkdown(x, vs.getDescription()); 247 if (vs.hasCopyright()) 248 generateCopyright(x, res); 249 } 250 boolean hasFragment = generateContentModeNotices(x, vs.getExpansion(), vs); 251 generateVersionNotice(x, vs.getExpansion(), vs); 252 253 if (ExtensionUtilities.hasExtension(vs.getExpansion(), ExtensionDefinitions.EXT_EXP_TOOCOSTLY)) { 254 String msg = null; 255 if (vs.getExpansion().getContains().isEmpty()) { 256 msg = context.formatPhrase(RenderingContext.VALUE_SET_TOO_COSTLY); 257 } else { 258 msg = context.formatPhrase(RenderingContext.VALUE_SET_CODE_SELEC, countMembership(vs)); 259 } 260 x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(msg); 261 } else { 262 int count = ValueSetUtilities.countExpansion(vs); 263 if (vs.getExpansion().hasTotal()) { 264 if (count != vs.getExpansion().getTotal()) { 265 x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px") 266 .addText(context.formatPhrase(hasFragment ? RenderingContext.VALUE_SET_HAS_AT_LEAST : RenderingContext.VALUE_SET_HAS, vs.getExpansion().getTotal(), count)); 267 } else { 268 x.para().tx(context.formatPhrase(hasFragment ? RenderingContext.VALUE_SET_CONTAINS_AT_LEAST : RenderingContext.VALUE_SET_CONTAINS, vs.getExpansion().getTotal())); 269 } 270 } else if (count == 1000) { 271 // it's possible that there's exactly 1000 codes, in which case wht we're about to do is wrong 272 // work in progress to tighten up the terminology system to always return a total... 273 String msg = context.formatPhrase(RenderingContext.VALUE_SET_SEL); 274 x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(msg); 275 } else { 276 x.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_NUMBER_CONCEPTS, count)); 277 } 278 } 279 280 281 boolean doLevel = false; 282 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 283 if (cc.hasContains()) { 284 doLevel = true; 285 break; 286 } 287 } 288 boolean doInactive = checkDoInactive(vs.getExpansion().getContains()); 289 boolean doDefinition = checkDoDefinition(vs.getExpansion().getContains()); 290 291 XhtmlNode t = x.table("codes", false); 292 XhtmlNode tr = t.tr(); 293 if (doLevel) 294 tr.td().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_LEVEL)); 295 tr.td().attribute("style", "white-space:nowrap").b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 296 tr.td().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_SYSTEM)); 297 XhtmlNode tdDisp = tr.td(); 298 String displang = vs.getLanguage(); 299 if (displang == null) { 300 displang = findParamValue(vs.getExpansion().getParameter(), "displayLanguage"); 301 } 302 if (displang == null) { 303 tdDisp.b().tx(context.formatPhrase(RenderingContext.TX_DISPLAY)); 304 } else { 305 tdDisp.b().tx(context.formatPhrase(RenderingContext.TX_DISPLAY_LANG, displang)); 306 } 307 boolean doDesignations = false; 308 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 309 scanForDesignations(vs, c, langs, designations); 310 } 311 scanForProperties(vs.getExpansion(), langs, properties); 312 if (doInactive) { 313 tr.td().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_INACTIVE)); 314 } 315 if (doDefinition) { 316 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINITION)); 317 doDesignations = false; 318 for (String n : Utilities.sorted(properties.keySet())) { 319 tr.td().b().ah(context.prefixLocalHref(properties.get(n))).addText(n); 320 } 321 } else { 322 for (String n : Utilities.sorted(properties.keySet())) { 323 tr.td().b().ah(context.prefixLocalHref(properties.get(n))).addText(n); 324 } 325 // if we're not doing definitions and we don't have too many languages, we'll do them in line 326 doDesignations = langs.size() + properties.size() + designations.size() < MAX_DESIGNATIONS_IN_LINE; 327 328 if (doDesignations) { 329 if (vs.hasLanguage()) { 330 tdDisp.tx(" - "+describeLang(vs.getLanguage())); 331 } 332 for (String url : designations.keySet()) { 333 tr.td().b().addText(designations.get(url)); 334 } 335 for (String lang : langs) { 336 tr.td().b().addText(describeVSLang(lang, displang)); 337 } 338 } 339 } 340 341 342 addMapHeaders(tr, maps); 343 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 344 addExpansionRowToTable(t, vs, c, 1, doLevel, doDefinition, doInactive, maps, langs, designations, doDesignations, properties, res); 345 } 346 347 // now, build observed languages 348 349 if (!doDesignations && langs.size() + designations.size() > 0) { 350 Collections.sort(langs); 351 if (designations.size() == 0) { 352 x.para().b().tx(context.formatPhrase(RenderingContext.GENERAL_ADD_LANG)); 353 } else if (langs.size() == 0) { 354 x.para().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_DESIG)); 355 } else { 356 x.para().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_ADD_DESIG)); 357 } 358 t = x.table("codes", false); 359 tr = t.tr(); 360 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 361 for (String url : designations.keySet()) { 362 tr.td().b().addText(designations.get(url)); 363 } 364 for (String lang : langs) { 365 tr.td().b().addText(describeLang(lang)); 366 } 367 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 368 addDesignationRow(c, t, langs, designations); 369 } 370 } 371 372 } 373 374 protected String describeVSLang(String lang, String displang) { 375 376 // special cases: 377 if ("fr-CA".equals(lang)) { 378 return "French (Canadian)"; // this one was omitted from the value set 379 } 380 ValueSet v = getContext().getWorker().findTxResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages"); 381 if (v != null) { 382 ConceptReferenceComponent l = null; 383 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 384 if (cc.getCode().equals(lang)) 385 l = cc; 386 } 387 if (l == null) { 388 if (lang.contains("-")) { 389 lang = lang.substring(0, lang.indexOf("-")); 390 } 391 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 392 if (cc.getCode().equals(lang)) { 393 l = cc; 394 break; 395 } 396 } 397 if (l == null) { 398 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 399 if (cc.getCode().startsWith(lang+"-")) { 400 l = cc; 401 break; 402 } 403 } 404 } 405 } 406 if (l != null) { 407 if (lang.contains("-")) 408 lang = lang.substring(0, lang.indexOf("-")); 409 String en = l.getDisplay(); 410 String nativelang = null; 411 for (ConceptReferenceDesignationComponent cd : l.getDesignation()) { 412 if (cd.getLanguage().equals(lang)) 413 nativelang = cd.getValue(); 414 } 415 return context.formatPhrase(langsMatch(lang, displang) ? RenderingContext.VALUE_SET_OTHER_DISPLAY : RenderingContext.TX_DISPLAY_LANG, nativelang == null ? en : nativelang); 416 } 417 } 418 return lang; 419 } 420 421 422 private boolean langsMatch(String lang, String displang) { 423 if (lang == null) { 424 return displang == null; 425 } else if (lang.equals(displang)) { 426 return true; 427 } else if (displang == null) { 428 return false; 429 } else { 430 String l1 = lang.contains("-") ? lang.substring(0, lang.indexOf("-")) : lang; 431 String l2 = displang.contains("-") ? displang.substring(0, displang.indexOf("-")) : displang; 432 return l1.equals(l2); 433 } 434 } 435 436 private void scanForProperties(ValueSetExpansionComponent exp, List<String> langs, Map<String, String> properties) { 437 properties.clear(); 438 for (ValueSetExpansionPropertyComponent pp : exp.getProperty()) { 439 if (pp.hasCode() && pp.hasUri() && anyActualproperties(exp.getContains(), pp.getCode())) { 440 properties.put(pp.getCode(), pp.getUri()); 441 } 442 } 443 } 444 445 private boolean anyActualproperties(List<ValueSetExpansionContainsComponent> contains, String pp) { 446 for (ValueSetExpansionContainsComponent c : contains) { 447 for (ConceptPropertyComponent cp : c.getProperty()) { 448 if (pp.equals(cp.getCode())) { 449 return true; 450 } 451 } 452 if (anyActualproperties(c.getContains(), pp)) { 453 return true; 454 } 455 } 456 return false; 457 } 458 459 private boolean generateContentModeNotices(XhtmlNode x, ValueSetExpansionComponent expansion, Resource vs) { 460 generateContentModeNotice(x, expansion, "example", context.formatPhrase(RenderingContext.VALUE_SET_EXP), vs); 461 return generateContentModeNotice(x, expansion, "fragment", context.formatPhrase(RenderingContext.VALUE_SET_EXP_FRAG), vs); 462 } 463 464 private boolean generateContentModeNotice(XhtmlNode x, ValueSetExpansionComponent expansion, String mode, String text, Resource vs) { 465 boolean res = false; 466 Multimap<String, String> versions = HashMultimap.create(); 467 for (ValueSetExpansionParameterComponent p : expansion.getParameter()) { 468 if (p.getName().equals(mode)) { 469 String[] parts = ((PrimitiveType) p.getValue()).asStringValue().split("\\|"); 470 if (parts.length == 2 && !Utilities.noString(parts[0])) 471 versions.put(parts[0], parts[1]); 472 } 473 } 474 if (versions.size() > 0) { 475 XhtmlNode div = null; 476 XhtmlNode ul = null; 477 boolean first = true; 478 for (String s : versions.keySet()) { 479 if (versions.size() == 1 && versions.get(s).size() == 1) { 480 for (String v : versions.get(s)) { // though there'll only be one 481 XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #ffcccc; padding: 8px; margin-bottom: 8px"); 482 p.tx(text+" "); 483 expRef(p, s, v, vs); 484 res = true; 485 } 486 } else { 487 for (String v : versions.get(s)) { 488 if (first) { 489 div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); 490 div.para().tx(text+"s: "); 491 ul = div.ul(); 492 first = false; 493 res = true; 494 } 495 expRef(ul.li(), s, v, vs); 496 } 497 } 498 } 499 } 500 return res; 501 } 502 503 private boolean checkDoSystem(ValueSet vs, ValueSet src) { 504 if (src != null) 505 vs = src; 506 return vs.hasCompose(); 507 } 508 509 private boolean IsNotFixedExpansion(ValueSet vs) { 510 if (vs.hasCompose()) 511 return false; 512 513 514 // it's not fixed if it has any includes that are not version fixed 515 for (ConceptSetComponent cc : vs.getCompose().getInclude()) { 516 if (cc.hasValueSet()) 517 return true; 518 if (!cc.hasVersion()) 519 return true; 520 } 521 return false; 522 } 523 524 525 526 527 private ConceptMapRenderInstructions findByTarget(DataType source) { 528 if (source == null) { 529 return null; 530 } 531 String src = source.primitiveValue(); 532 if (src == null) { 533 return null; 534 } 535 for (ConceptMapRenderInstructions t : renderingMaps) { 536 if (src.equals(t.getUrl())) 537 return t; 538 } 539 return null; 540 } 541 542 private Integer countMembership(ValueSet vs) { 543 int count = 0; 544 if (vs.hasExpansion()) 545 count = count + ValueSetUtilities.countExpansion(vs); 546 else { 547 if (vs.hasCompose()) { 548 if (vs.getCompose().hasExclude()) { 549 try { 550 ValueSetExpansionOutcome vse = getContext().getWorker().expandVS(vs, true, false); 551 count = 0; 552 count += ValueSetUtilities.countExpansion(vse.getValueset()); 553 return count; 554 } catch (Exception e) { 555 return null; 556 } 557 } 558 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 559 if (inc.hasFilter()) 560 return null; 561 if (!inc.hasConcept()) 562 return null; 563 count = count + inc.getConcept().size(); 564 } 565 } 566 } 567 return count; 568 } 569 570 571 private void addCSRef(XhtmlNode x, String url) { 572 CodeSystem cs = getContext().getWorker().fetchCodeSystem(url); 573 if (cs == null) { 574 x.code(url); 575 } else if (cs.hasWebPath()) { 576 x.ah(context.prefixLocalHref(cs.getWebPath())).tx(cs.present()); 577 } else { 578 x.code(url); 579 x.tx(" ("+cs.present()+")"); 580 } 581 } 582 583 @SuppressWarnings("rawtypes") 584 private void generateVersionNotice(XhtmlNode x, ValueSetExpansionComponent expansion, Resource vs) { 585 586 Multimap<String, String> versions = HashMultimap.create(); 587 Set<String> vlist = new HashSet<>(); 588 for (ValueSetExpansionParameterComponent p : expansion.getParameter()) { 589 if ((p.getName().startsWith("used-") || p.getName().equals("version")) && !vlist.contains(p.getValue().primitiveValue())) { 590 String name = p.getName().equals("version") ? "system" : p.getName().substring(5); 591 vlist.add(p.getValue().primitiveValue()); 592 String[] parts = ((PrimitiveType) p.getValue()).asStringValue().split("\\|"); 593 if (parts.length == 2 && !Utilities.noString(parts[0])) 594 versions.put(name+"|"+parts[0], parts[1]); 595 } 596 } 597 if (versions.size() > 0) { 598 XhtmlNode div = null; 599 XhtmlNode ul = null; 600 boolean first = true; 601 for (String s : Utilities.sorted(versions.keySet())) { 602 if (versions.size() == 1 && versions.get(s).size() == 1) { 603 for (String v : versions.get(s)) { // though there'll only be one 604 XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); 605 if (!vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) { 606 p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION)+" "); 607 } else if ("internal".equals(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))) { 608 p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION_INTERNAL)+" "); 609 } else { 610 p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION_SRVR, vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))+" "); 611 } 612 expRef(p, s, v, vs); 613 } 614 } else { 615 for (String v : versions.get(s)) { 616 if (first) { 617 div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); 618 if (!vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) { 619 div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS)); 620 } else if ("internal".equals(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))) { 621 div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS_INTERNAL)); 622 } else { 623 div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS_SRVR, vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))); 624 } 625 ul = div.ul(); 626 first = false; 627 } 628 expRef(ul.li(), s, v, vs); 629 } 630 } 631 } 632 } 633 } 634 635 private String findParamValue(List<ValueSetExpansionParameterComponent> list, String name) { 636 for (ValueSetExpansionParameterComponent p : list) { 637 if (name.equals(p.getName())) { 638 return p.getValue().primitiveValue(); 639 } 640 } 641 return null; 642 } 643 644 private void expRef(XhtmlNode x, String u, String v, Resource source) { 645 String t = u.contains("|") ? u.substring(0, u.indexOf("|")) : u; 646 u = u.substring(u.indexOf("|")+1); 647 // TODO Auto-generated method stub 648 if (u.equals("http://snomed.info/sct")) { 649 String[] parts = v.split("\\/"); 650 if (parts.length >= 5) { 651 String m = describeModule(parts[4]); 652 if (parts.length == 7) { 653 x.tx(context.formatPhrase(RenderingContext.VALUE_SET_SNOMED_ADD, m, formatSCTDate(parts[6]))); 654 } else { 655 x.tx(context.formatPhrase(RenderingContext.VALUE_SET_SNOMED, m)); 656 } 657 } else { 658 x.tx(displaySystem(u)+" "+ context.formatPhrase(RenderingContext.GENERAL_VER_LOW) + " " +v); 659 } 660 } else if (u.equals("http://loinc.org")) { 661 String vd = describeLoincVer(v); 662 if (vd != null) { 663 x.tx(context.formatPhrase(RenderingContext.VALUE_SET_LOINCV)+v+" ("+vd+")"); 664 } else { 665 x.tx(context.formatPhrase(RenderingContext.VALUE_SET_LOINCV)+v); 666 } 667 } else if (Utilities.noString(v)) { 668 CanonicalResource cr = (CanonicalResource) getContext().getWorker().fetchResource(Resource.class, u, source); 669 if (cr != null) { 670 if (cr.hasWebPath()) { 671 x.ah(context.prefixLocalHref(cr.getWebPath())).tx(t+" "+cr.present()+" "+ context.formatPhrase(RenderingContext.VALUE_SET_NO_VERSION)+cr.fhirType()+")"); 672 } else { 673 x.tx(t+" "+displaySystem(u)+" "+context.formatPhrase(RenderingContext.VALUE_SET_NO_VERSION)+cr.fhirType()+")"); 674 } 675 } else { 676 x.tx(t+" "+displaySystem(u)+" "+ context.formatPhrase(RenderingContext.VALUE_SET_NO_VER)); 677 } 678 } else { 679 CanonicalResource cr = (CanonicalResource) getContext().getWorker().fetchResource(Resource.class, u+"|"+v, source); 680 if (cr != null) { 681 if (cr.hasWebPath()) { 682 x.ah(context.prefixLocalHref(cr.getWebPath())).tx(t+" "+cr.present()+" v"+v+" ("+cr.fhirType()+")"); 683 } else { 684 x.tx(t+" "+displaySystem(u)+" v"+v+" ("+cr.fhirType()+")"); 685 } 686 } else { 687 x.tx(t+" "+displaySystem(u)+" "+ context.formatPhrase(RenderingContext.GENERAL_VER_LOW)+v); 688 } 689 } 690 } 691 692 private String describeLoincVer(String v) { 693 if ("2.67".equals(v)) return "Dec 2019"; 694 if ("2.66".equals(v)) return "Jun 2019"; 695 if ("2.65".equals(v)) return "Dec 2018"; 696 if ("2.64".equals(v)) return "Jun 2018"; 697 if ("2.63".equals(v)) return "Dec 2017"; 698 if ("2.61".equals(v)) return "Jun 2017"; 699 if ("2.59".equals(v)) return "Feb 2017"; 700 if ("2.58".equals(v)) return "Dec 2016"; 701 if ("2.56".equals(v)) return "Jun 2016"; 702 if ("2.54".equals(v)) return "Dec 2015"; 703 if ("2.52".equals(v)) return "Jun 2015"; 704 if ("2.50".equals(v)) return "Dec 2014"; 705 if ("2.48".equals(v)) return "Jun 2014"; 706 if ("2.46".equals(v)) return "Dec 2013"; 707 if ("2.44".equals(v)) return "Jun 2013"; 708 if ("2.42".equals(v)) return "Dec 2012"; 709 if ("2.40".equals(v)) return "Jun 2012"; 710 if ("2.38".equals(v)) return "Dec 2011"; 711 if ("2.36".equals(v)) return "Jun 2011"; 712 if ("2.34".equals(v)) return "Dec 2010"; 713 if ("2.32".equals(v)) return "Jun 2010"; 714 if ("2.30".equals(v)) return "Feb 2010"; 715 if ("2.29".equals(v)) return "Dec 2009"; 716 if ("2.27".equals(v)) return "Jul 2009"; 717 if ("2.26".equals(v)) return "Jan 2009"; 718 if ("2.24".equals(v)) return "Jul 2008"; 719 if ("2.22".equals(v)) return "Dec 2007"; 720 if ("2.21".equals(v)) return "Jun 2007"; 721 if ("2.19".equals(v)) return "Dec 2006"; 722 if ("2.17".equals(v)) return "Jun 2006"; 723 if ("2.16".equals(v)) return "Dec 2005"; 724 if ("2.15".equals(v)) return "Jun 2005"; 725 if ("2.14".equals(v)) return "Dec 2004"; 726 if ("2.13".equals(v)) return "Aug 2004"; 727 if ("2.12".equals(v)) return "Feb 2004"; 728 if ("2.10".equals(v)) return "Oct 2003"; 729 if ("2.09".equals(v)) return "May 2003"; 730 if ("2.08 ".equals(v)) return "Sep 2002"; 731 if ("2.07".equals(v)) return "Aug 2002"; 732 if ("2.05".equals(v)) return "Feb 2002"; 733 if ("2.04".equals(v)) return "Jan 2002"; 734 if ("2.03".equals(v)) return "Jul 2001"; 735 if ("2.02".equals(v)) return "May 2001"; 736 if ("2.01".equals(v)) return "Jan 2001"; 737 if ("2.00".equals(v)) return "Jan 2001"; 738 if ("1.0n".equals(v)) return "Feb 2000"; 739 if ("1.0ma".equals(v)) return "Aug 1999"; 740 if ("1.0m".equals(v)) return "Jul 1999"; 741 if ("1.0l".equals(v)) return "Jan 1998"; 742 if ("1.0ja".equals(v)) return "Oct 1997"; 743 return null; 744 } 745 746 private String formatSCTDate(String ds) { 747 SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd"); 748 Date date; 749 try { 750 date = format.parse(ds); 751 } catch (ParseException e) { 752 return ds; 753 } 754 return new SimpleDateFormat("dd-MMM yyyy").format(date); 755 } 756 757 private String describeModule(String module) { 758 switch (module) { 759 case "900000000000207008" : return context.formatPhrase(RenderingContext.VALUE_SET_INT); 760 case "449081005" : return context.formatPhrase(RenderingContext.VALUE_SET_SPAN); 761 case "11000221109" : return context.formatPhrase(RenderingContext.VALUE_SET_AR); 762 case "32506021000036107" : return context.formatPhrase(RenderingContext.VALUE_SET_AUS); 763 case "11000234105" : return context.formatPhrase(RenderingContext.VALUE_SET_AT); 764 case "11000172109" : return context.formatPhrase(RenderingContext.VALUE_SET_BE); 765 case "20621000087109" : return context.formatPhrase(RenderingContext.VALUE_SET_CA_EN); 766 case "20611000087101" : return context.formatPhrase(RenderingContext.VALUE_SET_CA); // was FR but was repurposed 767 case "554471000005108" : return context.formatPhrase(RenderingContext.VALUE_SET_DANISH); 768 case "11000181102 " : return context.formatPhrase(RenderingContext.VALUE_SET_EE); 769 case "11000229106" : return context.formatPhrase(RenderingContext.VALUE_SET_FI); 770 case "11000274103" : return context.formatPhrase(RenderingContext.VALUE_SET_DE); 771 case "1121000189102" : return context.formatPhrase(RenderingContext.VALUE_SET_IN); 772 case "11000220105" : return context.formatPhrase(RenderingContext.VALUE_SET_IE); 773 case "11000146104" : return context.formatPhrase(RenderingContext.VALUE_SET_DUTCH); 774 case "21000210109" : return context.formatPhrase(RenderingContext.VALUE_SET_NZ); 775 case "51000202101 " : return context.formatPhrase(RenderingContext.VALUE_SET_NO); 776 case "11000267109" : return context.formatPhrase(RenderingContext.VALUE_SET_KR); 777 case "900000001000122104" : return context.formatPhrase(RenderingContext.VALUE_ES_ES); 778 case "45991000052106" : return context.formatPhrase(RenderingContext.VALUE_SET_SWEDISH); 779 case "2011000195101" : return context.formatPhrase(RenderingContext.VALUE_SET_CH); 780 case "83821000000107" : return context.formatPhrase(RenderingContext.VALUE_SET_UK); 781 case "999000021000000109" : return context.formatPhrase(RenderingContext.VALUE_SET_UK_CLIN); 782 case "5631000179106" : return context.formatPhrase(RenderingContext.VALUE_SET_UY); 783 case "731000124108" : return context.formatPhrase(RenderingContext.VALUE_SET_US); 784 case "5991000124107" : return context.formatPhrase(RenderingContext.VALUE_SET_US_ICD10CM); 785 default: 786 return module; 787 } 788 } 789 790 private boolean hasVersionParameter(ValueSetExpansionComponent expansion) { 791 for (ValueSetExpansionParameterComponent p : expansion.getParameter()) { 792 if (p.getName().equals("version")) 793 return true; 794 } 795 return false; 796 } 797 798 private void addDesignationRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List<String> langs, Map<String, String> designations) { 799 XhtmlNode tr = t.tr(); 800 tr.td().addText(c.getCode()); 801 addDesignationsToRow(c, designations, tr); 802 addLangaugesToRow(c, langs, tr); 803 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 804 addDesignationRow(cc, t, langs, designations); 805 } 806 } 807 808 public void addDesignationsToRow(ValueSetExpansionContainsComponent c, Map<String, String> designations, XhtmlNode tr) { 809 for (String url : designations.keySet()) { 810 String d = null; 811 if (d == null) { 812 for (ConceptReferenceDesignationComponent dd : c.getDesignation()) { 813 if (url.equals(getUrlForDesignation(dd))) { 814 d = dd.getValue(); 815 } 816 } 817 } 818 tr.td().addText(d == null ? "" : d); 819 } 820 } 821 822 public void addLangaugesToRow(ValueSetExpansionContainsComponent c, List<String> langs, XhtmlNode tr) { 823 for (String lang : langs) { 824 String d = null; 825 for (Extension ext : c.getExtension()) { 826 if (ExtensionDefinitions.EXT_TRANSLATION.equals(ext.getUrl())) { 827 String l = ExtensionUtilities.readStringExtension(ext, "lang"); 828 if (lang.equals(l)) { 829 d = ExtensionUtilities.readStringExtension(ext, "content"); 830 } 831 } 832 } 833 if (d == null) { 834 for (ConceptReferenceDesignationComponent dd : c.getDesignation()) { 835 String l = dd.getLanguage(); 836 if (lang.equals(l)) { 837 d = dd.getValue(); 838 } 839 } 840 } 841 tr.td().addText(d == null ? "" : d); 842 } 843 } 844 845 846 private boolean checkDoDefinition(List<ValueSetExpansionContainsComponent> contains) { 847 for (ValueSetExpansionContainsComponent c : contains) { 848 CodeSystem cs = getContext().getWorker().fetchCodeSystem(c.getSystem()); 849 if (cs != null) { 850 ConceptDefinitionComponent cd = CodeSystemUtilities.getCode(cs, c.getCode()); 851 if (cd != null && cd.hasDefinition()) { 852 return true; 853 } 854 } 855 if (checkDoDefinition(c.getContains())) 856 return true; 857 } 858 return false; 859 } 860 861 private boolean checkDoInactive(List<ValueSetExpansionContainsComponent> contains) { 862 for (ValueSetExpansionContainsComponent c : contains) { 863 if (c.hasInactive()) { 864 return true; 865 } 866 if (checkDoInactive(c.getContains())) 867 return true; 868 } 869 return false; 870 } 871 872 873 private boolean allFromOneSystem(ValueSet vs) { 874 if (vs.getExpansion().getContains().isEmpty()) 875 return false; 876 String system = vs.getExpansion().getContains().get(0).getSystem(); 877 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 878 if (!checkSystemMatches(system, cc)) 879 return false; 880 } 881 return true; 882 } 883 884 private String getCsRef(String system) { 885 CodeSystem cs = getContext().getWorker().fetchCodeSystem(system); 886 return getCsRef(cs); 887 } 888 889 private <T extends Resource> String getCsRef(T cs) { 890 if (cs == null) { 891 return "?cs-n?"; 892 } 893 String ref = cs.getWebPath(); 894 if (ref == null) { 895 ref = cs.getUserString(UserDataNames.render_filename); 896 } 897 return ref == null ? null : ref.replace("\\", "/"); 898 } 899 900 private void scanForDesignations(ValueSet vs, ValueSetExpansionContainsComponent c, List<String> langs, Map<String, String> designations) { 901 for (Extension ext : c.getExtension()) { 902 if (ExtensionDefinitions.EXT_TRANSLATION.equals(ext.getUrl())) { 903 String lang = ExtensionUtilities.readStringExtension(ext, "lang"); 904 if (!Utilities.noString(lang) && !langs.contains(lang) && !isBaseLang(vs, lang)) { 905 langs.add(lang); 906 } 907 } 908 } 909 if (context.getDesignationMode() != DesignationMode.NONE) { 910 for (ConceptReferenceDesignationComponent d : c.getDesignation()) { 911 String lang = d.getLanguage(); 912 if (!Utilities.noString(lang)) { 913 if (!langs.contains(lang)) { 914 langs.add(lang); 915 } 916 } else if (context.getDesignationMode() == DesignationMode.ALL) { 917 // can we present this as a designation that we know? 918 String disp = getDisplayForDesignation(d); 919 String url = getUrlForDesignation(d); 920 if (disp == null) { 921 disp = getDisplayForUrl(url); 922 } 923 if (disp != null && !designations.containsKey(url) && url != null) { 924 designations.put(url, disp); 925 } 926 } 927 } 928 } 929 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 930 scanForDesignations(vs, cc, langs, designations); 931 } 932 } 933 934 private boolean isBaseLang(ValueSet vs, String lang) { 935 return (isDefLang(lang) && isDefLang(vs.getLanguage())) || langsMatch(lang, vs.getLanguage()); 936 } 937 938 private boolean isDefLang(String lang) { 939 return lang == null || "en".equals(lang) || "en-US".equals(lang); 940 } 941 942 private void scanForLangs(ValueSetExpansionContainsComponent c, List<String> langs) { 943 for (Extension ext : c.getExtension()) { 944 if (ExtensionDefinitions.EXT_TRANSLATION.equals(ext.getUrl())) { 945 String lang = ExtensionUtilities.readStringExtension(ext, "lang"); 946 if (!Utilities.noString(lang) && !langs.contains(lang)) { 947 langs.add(lang); 948 } 949 } 950 } 951 for (ConceptReferenceDesignationComponent d : c.getDesignation()) { 952 String lang = d.getLanguage(); 953 if (!Utilities.noString(lang) && !langs.contains(lang)) { 954 langs.add(lang); 955 } 956 } 957 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 958 scanForLangs(cc, langs); 959 } 960 } 961 962 private void addExpansionRowToTable(XhtmlNode t, ValueSet vs, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doDefinition, boolean doInactive, List<UsedConceptMap> maps, List<String> langs, Map<String, String> designations, boolean doDesignations, Map<String, String> properties, ResourceWrapper res) throws FHIRFormatError, DefinitionException, IOException { 963 XhtmlNode tr = t.tr(); 964 if (ValueSetUtilities.isDeprecated(vs, c)) { 965 tr.setAttribute("style", "background-color: #ffeeee"); 966 } 967 968 XhtmlNode td = tr.td(); 969 970 String tgt = makeAnchor(c.getSystem(), c.getCode()); 971 String pfx = res.getScopedId(); 972 td.an((context.prefixAnchor(pfx == null ? "" : pfx+"-")+tgt)); 973 974 if (doLevel) { 975 td.addText(Integer.toString(i)); 976 td = tr.td(); 977 } 978 String s = Utilities.padLeft("", '\u00A0', i*2); 979 td.attribute("style", "white-space:nowrap").addText(s); 980 addCodeToTable(c.getAbstract(), c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay(), td); 981 td = tr.td(); 982 if (context.isOids()) { 983 td.addText(getOid(c.getSystem())); 984 } else { 985 td.addText(c.getSystem()); 986 } 987 td = tr.td(); 988 if (c.hasDisplayElement()) 989 td.addText(c.getDisplay()); 990 991 if (doInactive) { 992 td = tr.td(); 993 if (c.getInactive()) { 994 td.tx(context.formatPhrase(RenderingContext.VALUE_SET_INACT)); 995 } 996 } 997 if (doDefinition) { 998 td = tr.td(); 999 CodeSystem cs = getContext().getWorker().fetchCodeSystem(c.getSystem()); 1000 if (cs != null) { 1001 String defn = CodeSystemUtilities.getCodeDefinition(cs, c.getCode()); 1002 if (hasMarkdownInDefinitions(cs)) { 1003 addMarkdown(td, defn, cs.getWebPath()); 1004 } else { 1005 td.tx(defn); 1006 } 1007 } 1008 } 1009 for (String n : Utilities.sorted(properties.keySet())) { 1010 td = tr.td(); 1011 String ps = getPropertyValue(c, n); 1012 if (!Utilities.noString(ps)) { 1013 td.addText(ps); 1014 } 1015 } 1016 for (UsedConceptMap m : maps) { 1017 td = tr.td(); 1018 List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap()); 1019 boolean first = true; 1020 for (TargetElementComponentWrapper mapping : mappings) { 1021 if (!first) 1022 td.br(); 1023 first = false; 1024 XhtmlNode span = td.span(null, mapping.comp.getRelationship().toString()); 1025 span.addText(getCharForRelationship(mapping.comp)); 1026 addRefToCode(td, mapping.group.getTarget(), null, m.getLink(), mapping.comp.getCode()); 1027 if (!Utilities.noString(mapping.comp.getComment())) 1028 td.i().tx("("+mapping.comp.getComment()+")"); 1029 } 1030 } 1031 if (doDesignations) { 1032 addDesignationsToRow(c, designations, tr); 1033 addLangaugesToRow(c, langs, tr); 1034 } 1035 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 1036 addExpansionRowToTable(t, vs, cc, i+1, doLevel, doDefinition, doInactive, maps, langs, designations, doDesignations, properties, res); 1037 } 1038 } 1039 1040 1041 private boolean hasMarkdownInDefinitions(CodeSystem cs) { 1042 if (!cs.hasUserData(UserDataNames.CS_MARKDOWN_FLAG)) { 1043 if (cs.hasExtension("http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown")) { 1044 cs.setUserData(UserDataNames.CS_MARKDOWN_FLAG, ExtensionUtilities.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown")); 1045 } else { 1046 cs.setUserData(UserDataNames.CS_MARKDOWN_FLAG, CodeSystemUtilities.hasMarkdownInDefinitions(cs, context.getMarkdown())); 1047 } 1048 } 1049 return (Boolean) cs.getUserData(UserDataNames.CS_MARKDOWN_FLAG); 1050 } 1051 1052 private String getOid(String system) { 1053 if (oidMap == null) { 1054 oidMap = new HashMap<>(); 1055 } 1056 String oid = oidMap.get(system); 1057 if (oid == null) { 1058 CodeSystem cs = context.getContext().fetchCodeSystem(system); 1059 if (cs != null) { 1060 oid = CodeSystemUtilities.getOID(cs); 1061 } 1062 if (oid == null) { 1063 oid = system; 1064 } 1065 oidMap.put(system, oid); 1066 } 1067 return oid; 1068 } 1069 1070 private String getPropertyValue(ValueSetExpansionContainsComponent c, String n) { 1071 for (ConceptPropertyComponent cp : c.getProperty()) { 1072 if (n.equals(cp.getCode())) { 1073 return cp.getValue().primitiveValue(); 1074 } 1075 } 1076 return null; 1077 } 1078 1079 private boolean checkSystemMatches(String system, ValueSetExpansionContainsComponent cc) { 1080 if (!system.equals(cc.getSystem())) 1081 return false; 1082 for (ValueSetExpansionContainsComponent cc1 : cc.getContains()) { 1083 if (!checkSystemMatches(system, cc1)) 1084 return false; 1085 } 1086 return true; 1087 } 1088 1089 private void addCodeToTable(boolean isAbstract, String system, String version, String code, String display, XhtmlNode td) { 1090 CodeSystem e = getContext().getWorker().fetchCodeSystem(system); 1091 if (e == null || (e.getContent() != org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode.COMPLETE && e.getContent() != org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode.FRAGMENT)) { 1092 if (isAbstract) 1093 td.i().setAttribute("title", context.formatPhrase(RenderingContext.VS_ABSTRACT_CODE_HINT)).addText(code); 1094 else if ("http://snomed.info/sct".equals(system)) { 1095 td.ah(context.prefixLocalHref(SnomedUtilities.getSctLink(version, code, context.getContext().getExpansionParameters()))).addText(code); 1096 } else if ("http://loinc.org".equals(system)) { 1097 td.ah(context.prefixLocalHref(LoincLinker.getLinkForCode(code))).addText(code); 1098 } else 1099 td.addText(code); 1100 } else { 1101 String href = context.fixReference(getCsRef(e)); 1102 if (href == null) { 1103 td.code().tx(code); 1104 } else { 1105 if (href.contains("#")) 1106 href = href + "-"+Utilities.nmtokenize(code); 1107 else 1108 href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code); 1109 if (isAbstract) 1110 td.ah(context.prefixLocalHref(href)).setAttribute("title", context.formatPhrase(RenderingContext.VS_ABSTRACT_CODE_HINT)).i().addText(code); 1111 else 1112 td.ah(context.prefixLocalHref(href)).addText(code); 1113 } 1114 } 1115 } 1116 1117 private void addRefToCode(XhtmlNode td, String target, String vslink, String code, String version) { 1118 addCodeToTable(false, target, version, code, null, td); 1119// CodeSystem cs = getContext().getWorker().fetchCodeSystem(target); 1120// String cslink = getCsRef(cs); 1121// String link = cslink != null ? cslink+"#"+cs.getId()+"-"+code : vslink+"#"+code; 1122// if (!Utilities.isAbsoluteUrl(link)) { 1123// link = getContext().getSpecificationLink()+link; 1124// } 1125// XhtmlNode a = td.ah(context.prefixLocalHref(link)); 1126// a.addText(code); 1127 } 1128 1129 private void generateComposition(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRException, IOException { 1130 List<String> langs = new ArrayList<String>(); 1131 Map<String, String> designations = new HashMap<>(); // map of url = description, where url is the designation code. Designations that are for languages won't make it into this list 1132 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 1133 scanDesignations(inc, langs, designations); 1134 } 1135 for (ConceptSetComponent inc : vs.getCompose().getExclude()) { 1136 scanDesignations(inc, langs, designations); 1137 } 1138 boolean doDesignations = langs.size() + designations.size() < MAX_DESIGNATIONS_IN_LINE; 1139 1140 if (header) { 1141 XhtmlNode h = x.h2(); 1142 h.addText(vs.present()); 1143 addMarkdown(x, vs.getDescription()); 1144 if (vs.hasCopyrightElement()) 1145 generateCopyright(x, res); 1146 } 1147 int index = 0; 1148 if (vs.getCompose().getInclude().size() == 1 && vs.getCompose().getExclude().size() == 0 && !VersionComparisonAnnotation.hasDeleted(vs.getCompose(), "include", "exclude")) { 1149 genInclude(status, x.ul(), vs.getCompose().getInclude().get(0), "Include", langs, doDesignations, maps, designations, index, vs); 1150 } else { 1151 XhtmlNode p = x.para(); 1152 p.tx(context.formatPhrase(RenderingContext.VALUE_SET_RULES_INC)); 1153 XhtmlNode ul = x.ul(); 1154 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 1155 genInclude(status, ul, inc, context.formatPhrase(RenderingContext.VALUE_SET_INC), langs, doDesignations, maps, designations, index, vs); 1156 index++; 1157 } 1158 for (Base inc : VersionComparisonAnnotation.getDeleted(vs.getCompose(), "include")) { 1159 genInclude(status, ul, (ConceptSetComponent) inc, context.formatPhrase(RenderingContext.VALUE_SET_INC), langs, doDesignations, maps, designations, index, vs); 1160 index++; 1161 } 1162 if (vs.getCompose().hasExclude() || VersionComparisonAnnotation.hasDeleted(vs.getCompose(), "exclude")) { 1163 p = x.para(); 1164 p.tx(context.formatPhrase(RenderingContext.VALUE_SET_RULES_EXC)); 1165 ul = x.ul(); 1166 for (ConceptSetComponent exc : vs.getCompose().getExclude()) { 1167 genInclude(status, ul, exc, context.formatPhrase(RenderingContext.VALUE_SET_EXCL), langs, doDesignations, maps, designations, index, vs); 1168 index++; 1169 } 1170 for (Base inc : VersionComparisonAnnotation.getDeleted(vs.getCompose(), "exclude")) { 1171 genInclude(status, ul, (ConceptSetComponent) inc, context.formatPhrase(RenderingContext.VALUE_SET_EXCL), langs, doDesignations, maps, designations, index, vs); 1172 index++; 1173 } 1174 } 1175 } 1176 1177 // now, build observed languages 1178 1179 if (!doDesignations && langs.size() + designations.size() > 0) { 1180 Collections.sort(langs); 1181 if (designations.size() == 0) { 1182 x.para().b().tx(context.formatPhrase(RenderingContext.GENERAL_ADD_LANG)); 1183 } else if (langs.size() == 0) { 1184 x.para().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_DESIG)); 1185 } else { 1186 x.para().b().tx(context.formatPhrase(RenderingContext.VALUE_SET_ADD_DESIG)); 1187 } 1188 XhtmlNode t = x.table("codes", false); 1189 XhtmlNode tr = t.tr(); 1190 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 1191 for (String url : designations.keySet()) { 1192 tr.td().b().addText(designations.get(url)); 1193 } 1194 for (String lang : langs) { 1195 tr.td().b().addText(describeLang(lang)); 1196 } 1197 for (ConceptSetComponent c : vs.getCompose().getInclude()) { 1198 for (ConceptReferenceComponent cc : c.getConcept()) { 1199 addDesignationRow(cc, t, langs, designations); 1200 } 1201 } 1202 } 1203 } 1204 1205 private void renderExpansionRules(XhtmlNode x, ConceptSetComponent inc, int index, Map<String, ConceptDefinitionComponent> definitions) throws FHIRException, IOException { 1206 String s = context.formatPhrase(RenderingContext.VALUE_SET_NOT_DEF); 1207 if (inc.hasExtension(ExtensionDefinitions.EXT_EXPAND_RULES)) { 1208 String rule = inc.getExtensionString(ExtensionDefinitions.EXT_EXPAND_RULES); 1209 if (rule != null) { 1210 switch (rule) { 1211 case "all-codes": s = context.formatPhrase(RenderingContext.VALUE_SET_ALL_CODE); 1212 case "ungrouped": s = context.formatPhrase(RenderingContext.VALUE_SET_NOT_FOUND); 1213 case "groups-only": s = context.formatPhrase(RenderingContext.VALUE_SET_CONT_STRUC); 1214 } 1215 } 1216 } 1217 x.br(); 1218 x.tx(s); 1219 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true, "exp"); 1220 TableModel model = gen.new TableModel("exp.h="+index, context.getRules() == GenerationRules.IG_PUBLISHER); 1221 model.setAlternating(true); 1222 model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingContext.GENERAL_CODE), context.formatPhrase(RenderingContext.VALUE_SET_CODE_ITEM), null, 0)); 1223 model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingContext.TX_DISPLAY), context.formatPhrase(RenderingContext.VALUE_SET_DISPLAY_ITEM), null, 0)); 1224 1225 for (Extension ext : inc.getExtensionsByUrl(ExtensionDefinitions.EXT_EXPAND_GROUP)) { 1226 renderExpandGroup(gen, model, ext, inc, definitions); 1227 } 1228 x.br(); 1229 x.tx("table"); 1230 XhtmlNode xn = gen.generate(model, context.getLocalPrefix(), 1, null); 1231 x.addChildNode(xn); 1232 } 1233 1234 private void renderExpandGroup(HierarchicalTableGenerator gen, TableModel model, Extension ext, ConceptSetComponent inc, Map<String, ConceptDefinitionComponent> definitions) { 1235 Row row = gen.new Row(); 1236 model.getRows().add(row); 1237 row.setIcon("icon_entry_blue.png", "entry"); 1238 String code = ext.getExtensionString("code"); 1239 if (code != null) { 1240 row.getCells().add(gen.new Cell(null, null, code, null, null)); 1241 row.getCells().add(gen.new Cell(null, null, getDisplayForCode(inc, code, definitions), null, null)); 1242 } else if (ext.hasId()) { 1243 row.getCells().add(gen.new Cell(null, null, "(#"+ext.getId()+")", null, null)); 1244 row.getCells().add(gen.new Cell(null, null, ext.getExtensionString("display"), null, null)); 1245 } else { 1246 row.getCells().add(gen.new Cell(null, null, null, null, null)); 1247 row.getCells().add(gen.new Cell(null, null, ext.getExtensionString("display"), null, null)); 1248 } 1249 for (Extension member : ext.getExtensionsByUrl("member")) { 1250 Row subRow = gen.new Row(); 1251 row.getSubRows().add(subRow); 1252 subRow.setIcon("icon_entry_blue.png", "entry"); 1253 String mc = member.getValue().primitiveValue(); 1254 // mc might be a reference to another expansion group - we check that first, or to a code in the compose 1255 if (mc.startsWith("#")) { 1256 // it's a reference by id 1257 subRow.getCells().add(gen.new Cell(null, null, "("+mc+")", null, null)); 1258 subRow.getCells().add(gen.new Cell(null, null, "group reference by id", null, null)); 1259 } else { 1260 Extension tgt = findTargetByCode(inc, mc); 1261 if (tgt != null) { 1262 subRow.getCells().add(gen.new Cell(null, null, mc, null, null)); 1263 subRow.getCells().add(gen.new Cell(null, null, "group reference by code", null, null)); 1264 } else { 1265 subRow.getCells().add(gen.new Cell(null, null, mc, null, null)); 1266 subRow.getCells().add(gen.new Cell(null, null, getDisplayForCode(inc, mc, definitions), null, null)); 1267 } 1268 } 1269 } 1270 } 1271 1272 private Extension findTargetByCode(ConceptSetComponent inc, String mc) { 1273 for (Extension ext : inc.getExtensionsByUrl(ExtensionDefinitions.EXT_EXPAND_GROUP)) { 1274 String code = ext.getExtensionString("code"); 1275 if (mc.equals(code)) { 1276 return ext; 1277 } 1278 } 1279 return null; 1280 } 1281 1282 private String getDisplayForCode(ConceptSetComponent inc, String code, Map<String, ConceptDefinitionComponent> definitions) { 1283 for (ConceptReferenceComponent cc : inc.getConcept()) { 1284 if (code.equals(cc.getCode())) { 1285 if (cc.hasDisplay()) { 1286 return cc.getDisplay(); 1287 } 1288 } 1289 } 1290 if (definitions.containsKey(code)) { 1291 return definitions.get(code).getDisplay(); 1292 } 1293 return null; 1294 } 1295 1296 private void scanDesignations(ConceptSetComponent inc, List<String> langs, Map<String, String> designations) { 1297 for (ConceptReferenceComponent cc : inc.getConcept()) { 1298 for (Extension ext : cc.getExtension()) { 1299 if (ExtensionDefinitions.EXT_TRANSLATION.equals(ext.getUrl())) { 1300 String lang = ExtensionUtilities.readStringExtension(ext, "lang"); 1301 if (!Utilities.noString(lang) && !langs.contains(lang)) { 1302 langs.add(lang); 1303 } 1304 } 1305 } 1306 for (ConceptReferenceDesignationComponent d : cc.getDesignation()) { 1307 String lang = d.getLanguage(); 1308 if (!Utilities.noString(lang) && !langs.contains(lang)) { 1309 langs.add(lang); 1310 } else { 1311 // can we present this as a designation that we know? 1312 String disp = getDisplayForDesignation(d); 1313 String url = getUrlForDesignation(d); 1314 if (disp == null) { 1315 disp = getDisplayForUrl(url); 1316 } 1317 if (disp != null && !designations.containsKey(url)) { 1318 designations.put(url, disp); 1319 } 1320 } 1321 } 1322 } 1323 } 1324 1325 private String getDisplayForUrl(String url) { 1326 if (url == null) { 1327 return null; 1328 } 1329 switch (url) { 1330 case "http://snomed.info/sct#900000000000003001": 1331 return context.formatPhrase(RenderingContext.VALUE_SET_SPEC_NAME); 1332 case "http://snomed.info/sct#900000000000013009": 1333 return context.formatPhrase(RenderingContext.VALUE_SET_SYNONYM); 1334 case "http://terminology.hl7.org/CodeSystem/designation-usage#display": 1335 return context.formatPhrase(RenderingContext.VALUE_SET_OTHER_DISPLAY); 1336 case "http://terminology.hl7.org/CodeSystem/hl7TermMaintInfra#preferredForLanguage": 1337 return context.formatPhrase(RenderingContext.VALUE_SET_OTHER_DISPLAY); 1338 default: 1339 // As specified in http://www.hl7.org/fhir/valueset-definitions.html#ValueSet.compose.include.concept.designation.use and in http://www.hl7.org/fhir/codesystem-definitions.html#CodeSystem.concept.designation.use the terminology binding is extensible. 1340 return url; 1341 } 1342 } 1343 1344 private String getUrlForDesignation(ConceptReferenceDesignationComponent d) { 1345 if (d.hasUse() && d.getUse().hasSystem() && d.getUse().hasCode()) { 1346 return d.getUse().getSystem()+"#"+d.getUse().getCode(); 1347 } else { 1348 return null; 1349 } 1350 } 1351 1352 private String getDisplayForDesignation(ConceptReferenceDesignationComponent d) { 1353 if (d.hasUse() && d.getUse().hasDisplay()) { 1354 return d.getUse().getDisplay(); 1355 } else { 1356 return null; 1357 } 1358 } 1359 1360 private void genInclude(RenderingStatus status, XhtmlNode ul, ConceptSetComponent inc, String type, List<String> langs, boolean doDesignations, List<UsedConceptMap> maps, Map<String, String> designations, int index, ValueSet vsRes) throws FHIRException, IOException { 1361 XhtmlNode li; 1362 li = ul.li(); 1363 li = renderStatus(inc, li); 1364 1365 Map<String, ConceptDefinitionComponent> definitions = new HashMap<>(); 1366 1367 if (inc.hasSystem()) { 1368 CodeSystem e = getContext().getWorker().fetchCodeSystem(inc.getSystem()); 1369 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 1370 li.addText(type+" "+ context.formatPhrase(RenderingContext.VALUE_SET_ALL_CODES_DEF) + " "); 1371 addCsRef(inc, li, e); 1372 } else { 1373 if (inc.getConcept().size() > 0) { 1374 li.addText(type+" "+ context.formatPhrase(RenderingContext.VALUE_SET_THESE_CODES_DEF) + " "); 1375 addCsRef(inc, li, e); 1376 if (inc.hasVersion()) { 1377 li.addText(" "+ context.formatPhrase(RenderingContext.GENERAL_VER_LOW) + " "); 1378 li.code(inc.getVersion()); 1379 } 1380 1381 // for performance reasons, we do all the fetching in one batch 1382 definitions = getConceptsForCodes(e, inc, vsRes, index); 1383 1384 1385 XhtmlNode t = li.table("none", false); 1386 boolean hasComments = false; 1387 boolean hasDefinition = false; 1388 for (ConceptReferenceComponent c : inc.getConcept()) { 1389 hasComments = hasComments || ExtensionHelper.hasExtension(c, ExtensionDefinitions.EXT_VS_COMMENT); 1390 ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode()); 1391 hasDefinition = hasDefinition || ((cc != null && cc.hasDefinition()) || ExtensionHelper.hasExtension(c, ExtensionDefinitions.EXT_DEFINITION)); 1392 } 1393 if (hasComments || hasDefinition) { 1394 status.setExtensions(true); 1395 } 1396 addMapHeaders(addTableHeaderRowStandard(t, false, true, hasDefinition, hasComments, false, false, null, langs, designations, doDesignations), maps); 1397 for (ConceptReferenceComponent c : inc.getConcept()) { 1398 renderConcept(inc, langs, doDesignations, maps, designations, definitions, t, hasComments, hasDefinition, c, inc.getVersion()); 1399 } 1400 for (Base b : VersionComparisonAnnotation.getDeleted(inc, "concept" )) { 1401 renderConcept(inc, langs, doDesignations, maps, designations, definitions, t, hasComments, hasDefinition, (ConceptReferenceComponent) b, inc.getVersion()); 1402 } 1403 } 1404 if (inc.getFilter().size() > 0) { 1405 li.addText(type+" "+ context.formatPhrase(RenderingContext.VALUE_SET_CODES_FROM)); 1406 addCsRef(inc, li, e); 1407 li.tx(" "+ context.formatPhrase(RenderingContext.VALUE_SET_WHERE)+" "); 1408 for (int i = 0; i < inc.getFilter().size(); i++) { 1409 ConceptSetFilterComponent f = inc.getFilter().get(i); 1410 if (i > 0) { 1411 if (i == inc.getFilter().size()-1) { 1412 li.tx(" "+ context.formatPhrase(RenderingContext.VALUE_SET_AND)+" "); 1413 } else { 1414 li.tx(context.formatPhrase(RenderingContext.VALUE_SET_COMMA)+" "); 1415 } 1416 } 1417 XhtmlNode wli = renderStatus(f, li); 1418 if (f.getOp() == FilterOperator.EXISTS) { 1419 if (f.getValue().equals("true")) { 1420 wli.tx(f.getProperty()+" "+ context.formatPhrase(RenderingContext.VALUE_SET_EXISTS)); 1421 } else { 1422 wli.tx(f.getProperty()+" "+ context.formatPhrase(RenderingContext.VALUE_SET_DOESNT_EXIST)); 1423 } 1424 } else { 1425 wli.tx(f.getProperty()+" "+describe(f.getOp())+" "); 1426 if (f.getValueElement().hasExtension(ExtensionDefinitions.EXT_CQF_EXP)) { 1427 Extension expE = f.getValueElement().getExtensionByUrl(ExtensionDefinitions.EXT_CQF_EXP); 1428 Expression exp = expE.getValueExpression(); 1429 wli.addText("(as calculated by "); 1430 wli.code().tx(exp.getExpression()); 1431 wli.addText(")"); 1432 } else { 1433 if (e != null && codeExistsInValueSet(e, f.getValue())) { 1434 String href = getContext().fixReference(getCsRef(e)); 1435 if (href == null) { 1436 wli.code().tx(f.getValue()); 1437 } else { 1438 if (href.contains("#")) 1439 href = href + "-"+Utilities.nmtokenize(f.getValue()); 1440 else 1441 href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue()); 1442 wli.ah(context.prefixLocalHref(href)).addText(f.getValue()); 1443 } 1444 } else if (inc.hasSystem()) { 1445 wli.addText(f.getValue()); 1446 ValidationResult vr = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions(), inc.getSystem(), inc.getVersion(), f.getValue(), null); 1447 if (vr.isOk() && vr.getDisplay() != null) { 1448 wli.tx(" ("+vr.getDisplay()+")"); 1449 } 1450 } else { 1451 wli.addText(f.getValue()); 1452 } 1453 } 1454 String disp = ExtensionUtilities.getDisplayHint(f); 1455 if (disp != null) 1456 wli.tx(" ("+disp+")"); 1457 } 1458 } 1459 } 1460 } 1461 if (inc.hasValueSet()) { 1462 li.tx(context.formatPhrase(RenderingContext.VALUE_SET_WHERE_CODES)+" "); 1463 boolean first = true; 1464 for (UriType vs : inc.getValueSet()) { 1465 if (first) 1466 first = false; 1467 else 1468 li.tx(", "); 1469 XhtmlNode wli = renderStatus(vs, li); 1470 AddVsRef(vs.asStringValue(), wli, vsRes); 1471 } 1472 } 1473 if (inc.hasExtension(ExtensionDefinitions.EXT_EXPAND_RULES) || inc.hasExtension(ExtensionDefinitions.EXT_EXPAND_GROUP)) { 1474 status.setExtensions(true); 1475 renderExpansionRules(li, inc, index, definitions); 1476 } 1477 } else { 1478 li.tx(context.formatMessagePlural(inc.getValueSet().size(), RenderingContext.VALUE_SET_IMPORT)+" "); 1479 if (inc.getValueSet().size() <= 2) { 1480 int i = 0; 1481 for (UriType vs : inc.getValueSet()) { 1482 if (i > 0) { 1483 if ( i < inc.getValueSet().size() - 1) { 1484 li.tx(", "); 1485 } else { 1486 li.tx(" and "); 1487 } 1488 } 1489 i++; 1490 XhtmlNode wli = renderStatus(vs, li); 1491 AddVsRef(vs.asStringValue(), wli, vsRes); 1492 } 1493 } else { 1494 XhtmlNode xul = li.ul(); 1495 for (UriType vs : inc.getValueSet()) { 1496 XhtmlNode wli = renderStatus(vs, xul.li()); 1497 AddVsRef(vs.asStringValue(), wli, vsRes); 1498 } 1499 1500 } 1501 } 1502 } 1503 1504 private void renderConcept(ConceptSetComponent inc, List<String> langs, boolean doDesignations, 1505 List<UsedConceptMap> maps, Map<String, String> designations, Map<String, ConceptDefinitionComponent> definitions, 1506 XhtmlNode t, boolean hasComments, boolean hasDefinition, ConceptReferenceComponent c, String version) { 1507 XhtmlNode tr = t.tr(); 1508 XhtmlNode td = renderStatusRow(c, t, tr); 1509 ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode()); 1510 addCodeToTable(false, inc.getSystem(), version, c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td); 1511 1512 td = tr.td(); 1513 if (!Utilities.noString(c.getDisplay())) 1514 renderStatus(c.getDisplayElement(), td).addText(c.getDisplay()); 1515 else if (VersionComparisonAnnotation.hasDeleted(c, "display")) { 1516 StringType d = (StringType) VersionComparisonAnnotation.getDeletedItem(c, "display"); 1517 renderStatus(d, td).addText(d.primitiveValue()); 1518 } else if (cc != null && !Utilities.noString(cc.getDisplay())) 1519 td.style("color: #cccccc").addText(cc.getDisplay()); 1520 1521 if (hasDefinition) { 1522 td = tr.td(); 1523 if (ExtensionHelper.hasExtension(c, ExtensionDefinitions.EXT_DEFINITION)) { 1524 td.addTextWithLineBreaks(ExtensionUtilities.readStringExtension(c, ExtensionDefinitions.EXT_DEFINITION)); 1525 } else if (cc != null && !Utilities.noString(cc.getDefinition())) { 1526 td.addTextWithLineBreaks(cc.getDefinition()); 1527 } 1528 } 1529 if (hasComments) { 1530 td = tr.td(); 1531 if (ExtensionHelper.hasExtension(c, ExtensionDefinitions.EXT_VS_COMMENT)) { 1532 td.addTextWithLineBreaks(context.formatPhrase(RenderingContext.VALUE_SET_NOTE, ExtensionUtilities.readStringExtension(c, ExtensionDefinitions.EXT_VS_COMMENT)+" ")); 1533 } 1534 } 1535 if (doDesignations) { 1536 addDesignationsToRow(c, designations, tr); 1537 addLangaugesToRow(c, langs, tr); 1538 } 1539 for (UsedConceptMap m : maps) { 1540 td = tr.td(); 1541 List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap()); 1542 boolean first = true; 1543 for (TargetElementComponentWrapper mapping : mappings) { 1544 if (!first) 1545 td.br(); 1546 first = false; 1547 XhtmlNode span = td.span(null, mapping.comp.getRelationship().toString()); 1548 span.addText(getCharForRelationship(mapping.comp)); 1549 addRefToCode(td, mapping.group.getTarget(), m.getLink(), mapping.comp.getCode(), version); 1550 if (!Utilities.noString(mapping.comp.getComment())) 1551 td.i().tx("("+mapping.comp.getComment()+")"); 1552 } 1553 } 1554 } 1555 1556 public void addDesignationsToRow(ConceptReferenceComponent c, Map<String, String> designations, XhtmlNode tr) { 1557 for (String url : designations.keySet()) { 1558 String d = null; 1559 if (d == null) { 1560 for (ConceptReferenceDesignationComponent dd : c.getDesignation()) { 1561 if (url.equals(getUrlForDesignation(dd))) { 1562 d = dd.getValue(); 1563 } 1564 } 1565 } 1566 tr.td().addText(d == null ? "" : d); 1567 } 1568 } 1569 1570 public void addLangaugesToRow(ConceptReferenceComponent c, List<String> langs, XhtmlNode tr) { 1571 for (String lang : langs) { 1572 String d = null; 1573 for (Extension ext : c.getExtension()) { 1574 if (ExtensionDefinitions.EXT_TRANSLATION.equals(ext.getUrl())) { 1575 String l = ExtensionUtilities.readStringExtension(ext, "lang"); 1576 if (lang.equals(l)) { 1577 d = ExtensionUtilities.readStringExtension(ext, "content"); 1578 } 1579 } 1580 } 1581 if (d == null) { 1582 for (ConceptReferenceDesignationComponent dd : c.getDesignation()) { 1583 String l = dd.getLanguage(); 1584 if (lang.equals(l)) { 1585 d = dd.getValue(); 1586 } 1587 } 1588 } 1589 tr.td().addText(d == null ? "" : d); 1590 } 1591 } 1592 1593 1594 private Map<String, ConceptDefinitionComponent> getConceptsForCodes(CodeSystem e, ConceptSetComponent inc, ValueSet source, int index) { 1595 if (e == null) { 1596 e = getContext().getWorker().fetchCodeSystem(inc.getSystem()); 1597 } 1598 1599 ValueSetExpansionComponent vse = null; 1600 if (!context.isNoSlowLookup()) { // && !getContext().getWorker().hasCache()) { removed GG 20220107 like what is this trying to do? 1601 try { 1602 1603 ValueSet vs = new ValueSet(); 1604 vs.setUrl(source.getUrl()+"-inc-"+index); 1605 vs.setStatus(PublicationStatus.ACTIVE); 1606 vs.setCompose(new ValueSetComposeComponent()); 1607 vs.getCompose().setInactive(false); 1608 vs.getCompose().getInclude().add(inc); 1609 1610 ValueSetExpansionOutcome vso = getContext().getWorker().expandVS(vs, true, false); 1611 ValueSet valueset = vso.getValueset(); 1612 if (valueset == null) 1613 throw new TerminologyServiceException(context.formatPhrase(RenderingContext.VALUE_SET_ERROR, vso.getError()+" ")); 1614 vse = valueset.getExpansion(); 1615 1616 } catch (Exception e1) { 1617 return null; 1618 } 1619 } 1620 1621 Map<String, ConceptDefinitionComponent> results = new HashMap<>(); 1622 List<CodingValidationRequest> serverList = new ArrayList<>(); 1623 1624 // 1st pass, anything we can resolve internally 1625 for (ConceptReferenceComponent cc : inc.getConcept()) { 1626 String code = cc.getCode(); 1627 ConceptDefinitionComponent v = null; 1628 if (e != null && code != null) { 1629 v = getConceptForCode(e.getConcept(), code); 1630 } 1631 if (v == null && vse != null) { 1632 v = getConceptForCodeFromExpansion(vse.getContains(), code); 1633 } 1634 if (v != null) { 1635 results.put(code, v); 1636 } else { 1637 serverList.add(new CodingValidationRequest(new Coding(inc.getSystem(), code, null))); 1638 } 1639 } 1640 if (!context.isNoSlowLookup() && !serverList.isEmpty()) { 1641 try { 1642 // todo: split this into 10k batches 1643 int i = 0; 1644 while (serverList.size() > i) { 1645 int len = Integer.min(serverList.size(), MAX_BATCH_VALIDATION_SIZE); 1646 List<CodingValidationRequest> list = serverList.subList(i, i+len); 1647 i += len; 1648 getContext().getWorker().validateCodeBatch(getContext().getTerminologyServiceOptions(), list, null, true); 1649 for (CodingValidationRequest vr : list) { 1650 ConceptDefinitionComponent v = vr.getResult().asConceptDefinition(); 1651 if (v != null) { 1652 results.put(vr.getCoding().getCode(), v); 1653 } 1654 } 1655 } 1656 } catch (Exception e1) { 1657 return null; 1658 } 1659 } 1660 return results; 1661 } 1662 1663 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> list, String code) { 1664 for (ConceptDefinitionComponent c : list) { 1665 if (code.equals(c.getCode())) 1666 return c; 1667 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 1668 if (v != null) 1669 return v; 1670 } 1671 return null; 1672 } 1673 1674 private ConceptDefinitionComponent getConceptForCodeFromExpansion(List<ValueSetExpansionContainsComponent> list, String code) { 1675 for (ValueSetExpansionContainsComponent c : list) { 1676 if (code.equals(c.getCode())) { 1677 ConceptDefinitionComponent res = new ConceptDefinitionComponent(); 1678 res.setCode(c.getCode()); 1679 res.setDisplay(c.getDisplay()); 1680 return res; 1681 } 1682 ConceptDefinitionComponent v = getConceptForCodeFromExpansion(c.getContains(), code); 1683 if (v != null) 1684 return v; 1685 } 1686 return null; 1687 } 1688 1689 1690 private boolean codeExistsInValueSet(CodeSystem cs, String code) { 1691 for (ConceptDefinitionComponent c : cs.getConcept()) { 1692 if (inConcept(code, c)) 1693 return true; 1694 } 1695 return false; 1696 } 1697 1698 1699 1700 private void addDesignationRow(ConceptReferenceComponent c, XhtmlNode t, List<String> langs, Map<String, String> designations) { 1701 XhtmlNode tr = t.tr(); 1702 tr.td().addText(c.getCode()); 1703 addDesignationsToRow(c, designations, tr); 1704 addLangaugesToRow(c, langs, tr); 1705 } 1706 1707 1708 private String describe(FilterOperator op) { 1709 if (op == null) 1710 return " "+ context.formatPhrase(RenderingContext.VALUE_SET_NULL); 1711 switch (op) { 1712 case EQUAL: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_EQUAL); 1713 case ISA: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_ISA); 1714 case ISNOTA: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_ISNOTA); 1715 case REGEX: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_REGEX); 1716 case NULL: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_NULLS); 1717 case IN: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_IN); 1718 case NOTIN: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_NOTIN); 1719 case DESCENDENTOF: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_DESCENDENTOF); 1720 case EXISTS: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_EXISTS); 1721 case GENERALIZES: return " "+ context.formatPhrase(RenderingContext.VALUE_SET_GENERALIZES); 1722 } 1723 return null; 1724 } 1725 1726 private boolean inConcept(String code, ConceptDefinitionComponent c) { 1727 if (c.hasCodeElement() && c.getCode().equals(code)) 1728 return true; 1729 for (ConceptDefinitionComponent g : c.getConcept()) { 1730 if (inConcept(code, g)) 1731 return true; 1732 } 1733 return false; 1734 } 1735 1736 1737 @Override 1738 protected void genSummaryTableContent(RenderingStatus status, XhtmlNode tbl, CanonicalResource cr) throws IOException { 1739 super.genSummaryTableContent(status, tbl, cr); 1740 1741 ValueSet vs = (ValueSet) cr; 1742 XhtmlNode tr; 1743 1744 if (CodeSystemUtilities.hasOID(vs)) { 1745 tr = tbl.tr(); 1746 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_OID)+":"); 1747 tr.td().tx(context.formatPhrase(RenderingContext.CODE_SYS_FOR_OID, CodeSystemUtilities.getOID(vs))); 1748 } 1749 } 1750 1751}