001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.text.ParseException; 005import java.text.SimpleDateFormat; 006import java.util.ArrayList; 007import java.util.Collections; 008import java.util.Date; 009import java.util.HashMap; 010import java.util.List; 011import java.util.Map; 012 013import org.hl7.fhir.exceptions.DefinitionException; 014import org.hl7.fhir.exceptions.FHIRException; 015import org.hl7.fhir.exceptions.FHIRFormatError; 016import org.hl7.fhir.exceptions.TerminologyServiceException; 017import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation; 018import org.hl7.fhir.r5.context.IWorkerContext.CodingValidationRequest; 019import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 020import org.hl7.fhir.r5.model.Base; 021import org.hl7.fhir.r5.model.BooleanType; 022import org.hl7.fhir.r5.model.CanonicalResource; 023import org.hl7.fhir.r5.model.CodeSystem; 024import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 025import org.hl7.fhir.r5.model.Coding; 026import org.hl7.fhir.r5.model.ConceptMap; 027import org.hl7.fhir.r5.model.DataType; 028import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 029import org.hl7.fhir.r5.model.Extension; 030import org.hl7.fhir.r5.model.ExtensionHelper; 031import org.hl7.fhir.r5.model.PrimitiveType; 032import org.hl7.fhir.r5.model.Resource; 033import org.hl7.fhir.r5.model.StringType; 034import org.hl7.fhir.r5.model.UriType; 035import org.hl7.fhir.r5.model.ValueSet; 036import org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent; 037import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 038import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 039import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 040import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 041import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; 042import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 043import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent; 044import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; 045import org.hl7.fhir.r5.renderers.utils.RenderingContext; 046import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 047import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 048import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 049import org.hl7.fhir.r5.terminologies.ValueSetUtilities; 050import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 051import org.hl7.fhir.r5.utils.ToolingExtensions; 052import org.hl7.fhir.utilities.LoincLinker; 053import org.hl7.fhir.utilities.Utilities; 054import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 055import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 056import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 057import org.hl7.fhir.utilities.xhtml.XhtmlNode; 058 059import com.google.common.collect.HashMultimap; 060import com.google.common.collect.Multimap; 061import com.ibm.icu.impl.locale.StringTokenIterator; 062 063public class ValueSetRenderer extends TerminologyRenderer { 064 065 public ValueSetRenderer(RenderingContext context) { 066 super(context); 067 } 068 069 public ValueSetRenderer(RenderingContext context, ResourceContext rcontext) { 070 super(context, rcontext); 071 } 072 073 private static final String ABSTRACT_CODE_HINT = "This code is not selectable ('Abstract')"; 074 075 private static final int MAX_DESIGNATIONS_IN_LINE = 5; 076 077 private List<ConceptMapRenderInstructions> renderingMaps = new ArrayList<ConceptMapRenderInstructions>(); 078 079 public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { 080 return render(x, (ValueSet) dr, false); 081 } 082 083 084 public boolean render(XhtmlNode x, ValueSet vs, boolean header) throws FHIRFormatError, DefinitionException, IOException { 085 List<UsedConceptMap> maps = findReleventMaps(vs); 086 087 boolean hasExtensions; 088 if (vs.hasExpansion()) { 089 // for now, we just accept an expansion if there is one 090 hasExtensions = generateExpansion(x, vs, header, maps); 091 } else { 092 hasExtensions = generateComposition(x, vs, header, maps); 093 } 094 return hasExtensions; 095 } 096 097 public void describe(XhtmlNode x, ValueSet vs) { 098 x.tx(display(vs)); 099 } 100 101 public String display(ValueSet vs) { 102 return vs.present(); 103 } 104 105 106 private List<UsedConceptMap> findReleventMaps(ValueSet vs) throws FHIRException { 107 List<UsedConceptMap> res = new ArrayList<UsedConceptMap>(); 108 for (ConceptMap cm : getContext().getWorker().fetchResourcesByType(ConceptMap.class)) { 109 if (isSource(vs, cm.getSourceScope())) { 110 ConceptMapRenderInstructions re = findByTarget(cm.getTargetScope()); 111 if (re == null) { 112 re = new ConceptMapRenderInstructions(cm.present(), cm.getUrl(), false); 113 } 114 if (re != null) { 115 ValueSet vst = cm.hasTargetScope() ? getContext().getWorker().fetchResource(ValueSet.class, cm.hasTargetScopeCanonicalType() ? cm.getTargetScopeCanonicalType().getValue() : cm.getTargetScopeUriType().asStringValue(), cm) : null; 116 res.add(new UsedConceptMap(re, vst == null ? cm.getWebPath() : vst.getWebPath(), cm)); 117 } 118 } 119 } 120 return res; 121 122// @Override 123// public List<ConceptMap> findMapsForSource(String url) throws FHIRException { 124// synchronized (lock) { 125// List<ConceptMap> res = new ArrayList<ConceptMap>(); 126// for (ConceptMap map : maps.getList()) { 127// if (((Reference) map.getSourceScope()).getReference().equals(url)) { 128// res.add(map); 129// } 130// } 131// return res; 132// } 133// } 134 135// Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>(); 136// for (ConceptMap a : context.getWorker().findMapsForSource(vs.getUrl())) { 137// String url = ""; 138// ValueSet vsr = context.getWorker().fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference()); 139// if (vsr != null) 140// url = (String) vsr.getUserData("filename"); 141// mymaps.put(a, url); 142// } 143// Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>(); 144// for (ConceptMap a : context.getWorker().findMapsForSource(cs.getValueSet())) { 145// String url = ""; 146// ValueSet vsr = context.getWorker().fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference()); 147// if (vsr != null) 148// url = (String) vsr.getUserData("filename"); 149// mymaps.put(a, url); 150// } 151 // also, look in the contained resources for a concept map 152// for (Resource r : cs.getContained()) { 153// if (r instanceof ConceptMap) { 154// ConceptMap cm = (ConceptMap) r; 155// if (((Reference) cm.getSource()).getReference().equals(cs.getValueSet())) { 156// String url = ""; 157// ValueSet vsr = context.getWorker().fetchResource(ValueSet.class, ((Reference) cm.getTarget()).getReference()); 158// if (vsr != null) 159// url = (String) vsr.getUserData("filename"); 160// mymaps.put(cm, url); 161// } 162// } 163// } 164 } 165 166 private boolean isSource(ValueSet vs, DataType source) { 167 return vs.hasUrl() && source != null && vs.getUrl().equals(source.primitiveValue()); 168 } 169 170 private boolean generateExpansion(XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException { 171 boolean hasExtensions = false; 172 List<String> langs = new ArrayList<String>(); 173 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 174 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 175 176 if (header) { 177 XhtmlNode h = x.addTag(getHeader()); 178 h.tx("Value Set Contents"); 179 if (IsNotFixedExpansion(vs)) 180 addMarkdown(x, vs.getDescription()); 181 if (vs.hasCopyright()) 182 generateCopyright(x, vs); 183 } 184 if (ToolingExtensions.hasExtension(vs.getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY)) { 185 List<Extension> exl = vs.getExpansion().getExtensionsByUrl(ToolingExtensions.EXT_EXP_TOOCOSTLY); 186 boolean other = false; 187 for (Extension ex : exl) { 188 if (ex.getValue() instanceof BooleanType) { 189 x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(vs.getExpansion().getContains().isEmpty() ? getContext().getTooCostlyNoteEmpty() : getContext().getTooCostlyNoteNotEmpty()); 190 } else if (!other) { 191 x.para().style("border: maroon 1px solid; background-color: #FFCCCC; font-weight: bold; padding: 8px").addText(vs.getExpansion().getContains().isEmpty() ? getContext().getTooCostlyNoteEmptyDependent() : getContext().getTooCostlyNoteNotEmptyDependent()); 192 other = true; 193 } 194 } 195 } else { 196 Integer count = countMembership(vs); 197 if (count == null) 198 x.para().tx("This value set does not contain a fixed number of concepts"); 199 else 200 x.para().tx("This value set contains "+count.toString()+" concepts"); 201 } 202 203 generateContentModeNotices(x, vs.getExpansion(), vs); 204 generateVersionNotice(x, vs.getExpansion(), vs); 205 206 boolean doLevel = false; 207 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 208 if (cc.hasContains()) { 209 doLevel = true; 210 break; 211 } 212 } 213 boolean doInactive = checkDoInactive(vs.getExpansion().getContains()); 214 boolean doDefinition = checkDoDefinition(vs.getExpansion().getContains()); 215 216 XhtmlNode t = x.table( "codes"); 217 XhtmlNode tr = t.tr(); 218 if (doLevel) 219 tr.td().b().tx("Level"); 220 tr.td().attribute("style", "white-space:nowrap").b().tx("Code"); 221 tr.td().b().tx("System"); 222 XhtmlNode tdDisp = tr.td(); 223 tdDisp.b().tx("Display"); 224 boolean doDesignations = false; 225 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 226 scanForDesignations(c, langs, designations); 227 } 228 scanForProperties(vs.getExpansion(), langs, properties); 229 if (doInactive) { 230 tr.td().b().tx("Inactive"); 231 } 232 if (doDefinition) { 233 tr.td().b().tx("Definition"); 234 doDesignations = false; 235 for (String n : Utilities.sorted(properties.keySet())) { 236 tr.td().b().ah(properties.get(n)).addText(n); 237 } 238 } else { 239 for (String n : Utilities.sorted(properties.keySet())) { 240 tr.td().b().ah(properties.get(n)).addText(n); 241 } 242 // if we're not doing definitions and we don't have too many languages, we'll do them in line 243 doDesignations = langs.size() + properties.size() + designations.size() < MAX_DESIGNATIONS_IN_LINE; 244 245 if (doDesignations) { 246 if (vs.hasLanguage()) { 247 tdDisp.tx(" - "+describeLang(vs.getLanguage())); 248 } 249 for (String url : designations.keySet()) { 250 tr.td().b().addText(designations.get(url)); 251 } 252 for (String lang : langs) { 253 tr.td().b().addText(describeLang(lang)); 254 } 255 } 256 } 257 258 259 addMapHeaders(tr, maps); 260 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 261 addExpansionRowToTable(t, vs, c, 1, doLevel, doDefinition, doInactive, maps, langs, designations, doDesignations, properties); 262 } 263 264 // now, build observed languages 265 266 if (!doDesignations && langs.size() + designations.size() > 0) { 267 Collections.sort(langs); 268 if (designations.size() == 0) { 269 x.para().b().tx("Additional Language Displays"); 270 } else if (langs.size() == 0) { 271 x.para().b().tx("Additional Designations"); 272 } else { 273 x.para().b().tx("Additional Designations and Language Displays"); 274 } 275 t = x.table("codes"); 276 tr = t.tr(); 277 tr.td().b().tx("Code"); 278 for (String url : designations.keySet()) { 279 tr.td().b().addText(designations.get(url)); 280 } 281 for (String lang : langs) { 282 tr.td().b().addText(describeLang(lang)); 283 } 284 for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 285 addDesignationRow(c, t, langs, designations); 286 } 287 } 288 289 return hasExtensions; 290 } 291 292 293 private void scanForProperties(ValueSetExpansionComponent exp, List<String> langs, Map<String, String> properties) { 294 properties.clear(); 295 for (ValueSetExpansionPropertyComponent pp : exp.getProperty()) { 296 if (pp.hasCode() && pp.hasUri() && anyActualproperties(exp.getContains(), pp.getCode())) { 297 properties.put(pp.getCode(), pp.getUri()); 298 } 299 } 300 } 301 302 private boolean anyActualproperties(List<ValueSetExpansionContainsComponent> contains, String pp) { 303 for (ValueSetExpansionContainsComponent c : contains) { 304 for (ConceptPropertyComponent cp : c.getProperty()) { 305 if (pp.equals(cp.getCode())) { 306 return true; 307 } 308 } 309 if (anyActualproperties(c.getContains(), pp)) { 310 return true; 311 } 312 } 313 return false; 314 } 315 316 private void generateContentModeNotices(XhtmlNode x, ValueSetExpansionComponent expansion, Resource vs) { 317 generateContentModeNotice(x, expansion, "example", "Expansion based on example code system", vs); 318 generateContentModeNotice(x, expansion, "fragment", "Expansion based on code system fragment", vs); 319 } 320 321 private void generateContentModeNotice(XhtmlNode x, ValueSetExpansionComponent expansion, String mode, String text, Resource vs) { 322 Multimap<String, String> versions = HashMultimap.create(); 323 for (ValueSetExpansionParameterComponent p : expansion.getParameter()) { 324 if (p.getName().equals(mode)) { 325 String[] parts = ((PrimitiveType) p.getValue()).asStringValue().split("\\|"); 326 if (parts.length == 2 && !Utilities.noString(parts[0])) 327 versions.put(parts[0], parts[1]); 328 } 329 } 330 if (versions.size() > 0) { 331 XhtmlNode div = null; 332 XhtmlNode ul = null; 333 boolean first = true; 334 for (String s : versions.keySet()) { 335 if (versions.size() == 1 && versions.get(s).size() == 1) { 336 for (String v : versions.get(s)) { // though there'll only be one 337 XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #ffcccc; padding: 8px; margin-bottom: 8px"); 338 p.tx(text+" "); 339 expRef(p, s, v, vs); 340 } 341 } else { 342 for (String v : versions.get(s)) { 343 if (first) { 344 div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); 345 div.para().tx(text+"s: "); 346 ul = div.ul(); 347 first = false; 348 } 349 expRef(ul.li(), s, v, vs); 350 } 351 } 352 } 353 } 354 } 355 356 private boolean checkDoSystem(ValueSet vs, ValueSet src) { 357 if (src != null) 358 vs = src; 359 return vs.hasCompose(); 360 } 361 362 private boolean IsNotFixedExpansion(ValueSet vs) { 363 if (vs.hasCompose()) 364 return false; 365 366 367 // it's not fixed if it has any includes that are not version fixed 368 for (ConceptSetComponent cc : vs.getCompose().getInclude()) { 369 if (cc.hasValueSet()) 370 return true; 371 if (!cc.hasVersion()) 372 return true; 373 } 374 return false; 375 } 376 377 378 379 380 private ConceptMapRenderInstructions findByTarget(DataType source) { 381 if (source == null) { 382 return null; 383 } 384 String src = source.primitiveValue(); 385 if (src == null) { 386 return null; 387 } 388 for (ConceptMapRenderInstructions t : renderingMaps) { 389 if (src.equals(t.getUrl())) 390 return t; 391 } 392 return null; 393 } 394 395 396 private Integer countMembership(ValueSet vs) { 397 int count = 0; 398 if (vs.hasExpansion()) 399 count = count + conceptCount(vs.getExpansion().getContains()); 400 else { 401 if (vs.hasCompose()) { 402 if (vs.getCompose().hasExclude()) { 403 try { 404 ValueSetExpansionOutcome vse = getContext().getWorker().expandVS(vs, true, false); 405 count = 0; 406 count += conceptCount(vse.getValueset().getExpansion().getContains()); 407 return count; 408 } catch (Exception e) { 409 return null; 410 } 411 } 412 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 413 if (inc.hasFilter()) 414 return null; 415 if (!inc.hasConcept()) 416 return null; 417 count = count + inc.getConcept().size(); 418 } 419 } 420 } 421 return count; 422 } 423 424 private int conceptCount(List<ValueSetExpansionContainsComponent> list) { 425 int count = 0; 426 for (ValueSetExpansionContainsComponent c : list) { 427 if (!c.getAbstract()) 428 count++; 429 count = count + conceptCount(c.getContains()); 430 } 431 return count; 432 } 433 434 private void addCSRef(XhtmlNode x, String url) { 435 CodeSystem cs = getContext().getWorker().fetchCodeSystem(url); 436 if (cs == null) { 437 x.code(url); 438 } else if (cs.hasWebPath()) { 439 x.ah(cs.getWebPath()).tx(cs.present()); 440 } else { 441 x.code(url); 442 x.tx(" ("+cs.present()+")"); 443 } 444 } 445 446 @SuppressWarnings("rawtypes") 447 private void generateVersionNotice(XhtmlNode x, ValueSetExpansionComponent expansion, Resource vs) { 448 Multimap<String, String> versions = HashMultimap.create(); 449 for (ValueSetExpansionParameterComponent p : expansion.getParameter()) { 450 if (p.getName().startsWith("used-") || p.getName().equals("version")) { 451 String name = p.getName().equals("version") ? "system" : p.getName().substring(5); 452 String[] parts = ((PrimitiveType) p.getValue()).asStringValue().split("\\|"); 453 if (parts.length == 2 && !Utilities.noString(parts[0])) 454 versions.put(name+"|"+parts[0], parts[1]); 455 } 456 } 457 if (versions.size() > 0) { 458 XhtmlNode div = null; 459 XhtmlNode ul = null; 460 boolean first = true; 461 for (String s : Utilities.sorted(versions.keySet())) { 462 if (versions.size() == 1 && versions.get(s).size() == 1) { 463 for (String v : versions.get(s)) { // though there'll only be one 464 XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); 465 p.tx("Expansion based on "); 466 expRef(p, s, v, vs); 467 } 468 } else { 469 for (String v : versions.get(s)) { 470 if (first) { 471 div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); 472 div.para().tx("Expansion based on: "); 473 ul = div.ul(); 474 first = false; 475 } 476 expRef(ul.li(), s, v, vs); 477 } 478 } 479 } 480 } 481 } 482 483 private void expRef(XhtmlNode x, String u, String v, Resource source) { 484 String t = u.contains("|") ? u.substring(0, u.indexOf("|")) : u; 485 u = u.substring(u.indexOf("|")+1); 486 // TODO Auto-generated method stub 487 if (u.equals("http://snomed.info/sct")) { 488 String[] parts = v.split("\\/"); 489 if (parts.length >= 5) { 490 String m = describeModule(parts[4]); 491 if (parts.length == 7) { 492 x.tx("SNOMED CT "+m+" edition "+formatSCTDate(parts[6])); 493 } else { 494 x.tx("SNOMED CT "+m+" edition"); 495 } 496 } else { 497 x.tx(describeSystem(u)+" version "+v); 498 } 499 } else if (u.equals("http://loinc.org")) { 500 String vd = describeLoincVer(v); 501 if (vd != null) { 502 x.tx("Loinc v"+v+" ("+vd+")"); 503 } else { 504 x.tx("Loinc v"+v); 505 } 506 } else if (Utilities.noString(v)) { 507 CanonicalResource cr = (CanonicalResource) getContext().getWorker().fetchResource(Resource.class, u, source); 508 if (cr != null) { 509 if (cr.hasWebPath()) { 510 x.ah(cr.getWebPath()).tx(t+" "+cr.present()+" (no version) ("+cr.fhirType()+")"); 511 } else { 512 x.tx(t+" "+describeSystem(u)+" (no version) ("+cr.fhirType()+")"); 513 } 514 } else { 515 x.tx(t+" "+describeSystem(u)+" (no version)"); 516 } 517 } else { 518 CanonicalResource cr = (CanonicalResource) getContext().getWorker().fetchResource(Resource.class, u+"|"+v, source); 519 if (cr != null) { 520 if (cr.hasWebPath()) { 521 x.ah(cr.getWebPath()).tx(t+" "+cr.present()+" v"+v+" ("+cr.fhirType()+")"); 522 } else { 523 x.tx(t+" "+describeSystem(u)+" v"+v+" ("+cr.fhirType()+")"); 524 } 525 } else { 526 x.tx(t+" "+describeSystem(u)+" version "+v); 527 } 528 } 529 } 530 531 private String describeLoincVer(String v) { 532 if ("2.67".equals(v)) return "Dec 2019"; 533 if ("2.66".equals(v)) return "Jun 2019"; 534 if ("2.65".equals(v)) return "Dec 2018"; 535 if ("2.64".equals(v)) return "Jun 2018"; 536 if ("2.63".equals(v)) return "Dec 2017"; 537 if ("2.61".equals(v)) return "Jun 2017"; 538 if ("2.59".equals(v)) return "Feb 2017"; 539 if ("2.58".equals(v)) return "Dec 2016"; 540 if ("2.56".equals(v)) return "Jun 2016"; 541 if ("2.54".equals(v)) return "Dec 2015"; 542 if ("2.52".equals(v)) return "Jun 2015"; 543 if ("2.50".equals(v)) return "Dec 2014"; 544 if ("2.48".equals(v)) return "Jun 2014"; 545 if ("2.46".equals(v)) return "Dec 2013"; 546 if ("2.44".equals(v)) return "Jun 2013"; 547 if ("2.42".equals(v)) return "Dec 2012"; 548 if ("2.40".equals(v)) return "Jun 2012"; 549 if ("2.38".equals(v)) return "Dec 2011"; 550 if ("2.36".equals(v)) return "Jun 2011"; 551 if ("2.34".equals(v)) return "Dec 2010"; 552 if ("2.32".equals(v)) return "Jun 2010"; 553 if ("2.30".equals(v)) return "Feb 2010"; 554 if ("2.29".equals(v)) return "Dec 2009"; 555 if ("2.27".equals(v)) return "Jul 2009"; 556 if ("2.26".equals(v)) return "Jan 2009"; 557 if ("2.24".equals(v)) return "Jul 2008"; 558 if ("2.22".equals(v)) return "Dec 2007"; 559 if ("2.21".equals(v)) return "Jun 2007"; 560 if ("2.19".equals(v)) return "Dec 2006"; 561 if ("2.17".equals(v)) return "Jun 2006"; 562 if ("2.16".equals(v)) return "Dec 2005"; 563 if ("2.15".equals(v)) return "Jun 2005"; 564 if ("2.14".equals(v)) return "Dec 2004"; 565 if ("2.13".equals(v)) return "Aug 2004"; 566 if ("2.12".equals(v)) return "Feb 2004"; 567 if ("2.10".equals(v)) return "Oct 2003"; 568 if ("2.09".equals(v)) return "May 2003"; 569 if ("2.08 ".equals(v)) return "Sep 2002"; 570 if ("2.07".equals(v)) return "Aug 2002"; 571 if ("2.05".equals(v)) return "Feb 2002"; 572 if ("2.04".equals(v)) return "Jan 2002"; 573 if ("2.03".equals(v)) return "Jul 2001"; 574 if ("2.02".equals(v)) return "May 2001"; 575 if ("2.01".equals(v)) return "Jan 2001"; 576 if ("2.00".equals(v)) return "Jan 2001"; 577 if ("1.0n".equals(v)) return "Feb 2000"; 578 if ("1.0ma".equals(v)) return "Aug 1999"; 579 if ("1.0m".equals(v)) return "Jul 1999"; 580 if ("1.0l".equals(v)) return "Jan 1998"; 581 if ("1.0ja".equals(v)) return "Oct 1997"; 582 return null; 583 } 584 585 private String formatSCTDate(String ds) { 586 SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd"); 587 Date date; 588 try { 589 date = format.parse(ds); 590 } catch (ParseException e) { 591 return ds; 592 } 593 return new SimpleDateFormat("dd-MMM yyyy").format(date); 594 } 595 596 private String describeModule(String module) { 597 if ("900000000000207008".equals(module)) 598 return "International"; 599 if ("731000124108".equals(module)) 600 return "United States"; 601 if ("32506021000036107".equals(module)) 602 return "Australian"; 603 if ("449081005".equals(module)) 604 return "Spanish"; 605 if ("554471000005108".equals(module)) 606 return "Danish"; 607 if ("11000146104".equals(module)) 608 return "Dutch"; 609 if ("45991000052106".equals(module)) 610 return "Swedish"; 611 if ("999000041000000102".equals(module)) 612 return "United Kingdon"; 613 return module; 614 } 615 616 private boolean hasVersionParameter(ValueSetExpansionComponent expansion) { 617 for (ValueSetExpansionParameterComponent p : expansion.getParameter()) { 618 if (p.getName().equals("version")) 619 return true; 620 } 621 return false; 622 } 623 624 private void addDesignationRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List<String> langs, Map<String, String> designations) { 625 XhtmlNode tr = t.tr(); 626 tr.td().addText(c.getCode()); 627 addDesignationsToRow(c, designations, tr); 628 addLangaugesToRow(c, langs, tr); 629 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 630 addDesignationRow(cc, t, langs, designations); 631 } 632 } 633 634 public void addDesignationsToRow(ValueSetExpansionContainsComponent c, Map<String, String> designations, XhtmlNode tr) { 635 for (String url : designations.keySet()) { 636 String d = null; 637 if (d == null) { 638 for (ConceptReferenceDesignationComponent dd : c.getDesignation()) { 639 if (url.equals(getUrlForDesignation(dd))) { 640 d = dd.getValue(); 641 } 642 } 643 } 644 tr.td().addText(d == null ? "" : d); 645 } 646 } 647 648 public void addLangaugesToRow(ValueSetExpansionContainsComponent c, List<String> langs, XhtmlNode tr) { 649 for (String lang : langs) { 650 String d = null; 651 for (Extension ext : c.getExtension()) { 652 if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { 653 String l = ToolingExtensions.readStringExtension(ext, "lang"); 654 if (lang.equals(l)) { 655 d = ToolingExtensions.readStringExtension(ext, "content"); 656 } 657 } 658 } 659 if (d == null) { 660 for (ConceptReferenceDesignationComponent dd : c.getDesignation()) { 661 String l = dd.getLanguage(); 662 if (lang.equals(l)) { 663 d = dd.getValue(); 664 } 665 } 666 } 667 tr.td().addText(d == null ? "" : d); 668 } 669 } 670 671 672 private boolean checkDoDefinition(List<ValueSetExpansionContainsComponent> contains) { 673 for (ValueSetExpansionContainsComponent c : contains) { 674 CodeSystem cs = getContext().getWorker().fetchCodeSystem(c.getSystem()); 675 if (cs != null) { 676 ConceptDefinitionComponent cd = CodeSystemUtilities.getCode(cs, c.getCode()); 677 if (cd != null && cd.hasDefinition()) { 678 return true; 679 } 680 } 681 if (checkDoDefinition(c.getContains())) 682 return true; 683 } 684 return false; 685 } 686 687 private boolean checkDoInactive(List<ValueSetExpansionContainsComponent> contains) { 688 for (ValueSetExpansionContainsComponent c : contains) { 689 if (c.hasInactive()) { 690 return true; 691 } 692 if (checkDoInactive(c.getContains())) 693 return true; 694 } 695 return false; 696 } 697 698 699 private boolean allFromOneSystem(ValueSet vs) { 700 if (vs.getExpansion().getContains().isEmpty()) 701 return false; 702 String system = vs.getExpansion().getContains().get(0).getSystem(); 703 for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { 704 if (!checkSystemMatches(system, cc)) 705 return false; 706 } 707 return true; 708 } 709 710 private String getCsRef(String system) { 711 CodeSystem cs = getContext().getWorker().fetchCodeSystem(system); 712 return getCsRef(cs); 713 } 714 715 private <T extends Resource> String getCsRef(T cs) { 716 if (cs == null) { 717 return "?cs-n?"; 718 } 719 String ref = (String) cs.getUserData("filename"); 720 if (ref == null) 721 ref = (String) cs.getWebPath(); 722 if (ref == null) 723 return "?ngen-14?.html"; 724 if (!ref.contains(".html")) 725 ref = ref + ".html"; 726 return ref.replace("\\", "/"); 727 } 728 729 private void scanForDesignations(ValueSetExpansionContainsComponent c, List<String> langs, Map<String, String> designations) { 730 for (Extension ext : c.getExtension()) { 731 if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { 732 String lang = ToolingExtensions.readStringExtension(ext, "lang"); 733 if (!Utilities.noString(lang) && !langs.contains(lang)) { 734 langs.add(lang); 735 } 736 } 737 } 738 for (ConceptReferenceDesignationComponent d : c.getDesignation()) { 739 String lang = d.getLanguage(); 740 if (!Utilities.noString(lang) && !langs.contains(lang)) { 741 langs.add(lang); 742 } else { 743 // can we present this as a designation that we know? 744 String disp = getDisplayForDesignation(d); 745 String url = getUrlForDesignation(d); 746 if (disp == null) { 747 disp = getDisplayForUrl(url); 748 } 749 if (disp != null && !designations.containsKey(url) && url != null) { 750 designations.put(url, disp); 751 } 752 } 753 } 754 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 755 scanForDesignations(cc, langs, designations); 756 } 757 } 758 759 private void scanForLangs(ValueSetExpansionContainsComponent c, List<String> langs) { 760 for (Extension ext : c.getExtension()) { 761 if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { 762 String lang = ToolingExtensions.readStringExtension(ext, "lang"); 763 if (!Utilities.noString(lang) && !langs.contains(lang)) { 764 langs.add(lang); 765 } 766 } 767 } 768 for (ConceptReferenceDesignationComponent d : c.getDesignation()) { 769 String lang = d.getLanguage(); 770 if (!Utilities.noString(lang) && !langs.contains(lang)) { 771 langs.add(lang); 772 } 773 } 774 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 775 scanForLangs(cc, langs); 776 } 777 } 778 779 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) throws FHIRFormatError, DefinitionException, IOException { 780 XhtmlNode tr = t.tr(); 781 if (ValueSetUtilities.isDeprecated(vs, c)) { 782 tr.setAttribute("style", "background-color: #ffeeee"); 783 } 784 785 XhtmlNode td = tr.td(); 786 787 String tgt = makeAnchor(c.getSystem(), c.getCode()); 788 td.an(tgt); 789 790 if (doLevel) { 791 td.addText(Integer.toString(i)); 792 td = tr.td(); 793 } 794 String s = Utilities.padLeft("", '\u00A0', i*2); 795 td.attribute("style", "white-space:nowrap").addText(s); 796 addCodeToTable(c.getAbstract(), c.getSystem(), c.getCode(), c.getDisplay(), td); 797 td = tr.td(); 798 td.addText(c.getSystem()); 799 td = tr.td(); 800 if (c.hasDisplayElement()) 801 td.addText(c.getDisplay()); 802 803 if (doInactive) { 804 td = tr.td(); 805 if (c.getInactive()) { 806 td.tx("inactive"); 807 } 808 } 809 if (doDefinition) { 810 td = tr.td(); 811 CodeSystem cs = getContext().getWorker().fetchCodeSystem(c.getSystem()); 812 if (cs != null) { 813 String defn = CodeSystemUtilities.getCodeDefinition(cs, c.getCode()); 814 addMarkdown(td, defn, cs.getWebPath()); 815 } 816 } 817 for (String n : Utilities.sorted(properties.keySet())) { 818 td = tr.td(); 819 String ps = getPropertyValue(c, n); 820 if (!Utilities.noString(ps)) { 821 td.addText(ps); 822 } 823 } 824 for (UsedConceptMap m : maps) { 825 td = tr.td(); 826 List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap()); 827 boolean first = true; 828 for (TargetElementComponentWrapper mapping : mappings) { 829 if (!first) 830 td.br(); 831 first = false; 832 XhtmlNode span = td.span(null, mapping.comp.getRelationship().toString()); 833 span.addText(getCharForRelationship(mapping.comp)); 834 addRefToCode(td, mapping.group.getTarget(), m.getLink(), mapping.comp.getCode()); 835 if (!Utilities.noString(mapping.comp.getComment())) 836 td.i().tx("("+mapping.comp.getComment()+")"); 837 } 838 } 839 if (doDesignations) { 840 addDesignationsToRow(c, designations, tr); 841 addLangaugesToRow(c, langs, tr); 842 } 843 for (ValueSetExpansionContainsComponent cc : c.getContains()) { 844 addExpansionRowToTable(t, vs, cc, i+1, doLevel, doDefinition, doInactive, maps, langs, designations, doDesignations, properties); 845 } 846 } 847 848 849 850 851 852 private String getPropertyValue(ValueSetExpansionContainsComponent c, String n) { 853 for (ConceptPropertyComponent cp : c.getProperty()) { 854 if (n.equals(cp.getCode())) { 855 return cp.getValue().primitiveValue(); 856 } 857 } 858 return null; 859 } 860 861 private boolean checkSystemMatches(String system, ValueSetExpansionContainsComponent cc) { 862 if (!system.equals(cc.getSystem())) 863 return false; 864 for (ValueSetExpansionContainsComponent cc1 : cc.getContains()) { 865 if (!checkSystemMatches(system, cc1)) 866 return false; 867 } 868 return true; 869 } 870 871 private void addCodeToTable(boolean isAbstract, String system, String code, String display, XhtmlNode td) { 872 CodeSystem e = getContext().getWorker().fetchCodeSystem(system); 873 if (e == null || (e.getContent() != org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode.COMPLETE && e.getContent() != org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode.FRAGMENT)) { 874 if (isAbstract) 875 td.i().setAttribute("title", ABSTRACT_CODE_HINT).addText(code); 876 else if ("http://snomed.info/sct".equals(system)) { 877 td.ah(sctLink(code)).addText(code); 878 } else if ("http://loinc.org".equals(system)) { 879 td.ah(LoincLinker.getLinkForCode(code)).addText(code); 880 } else 881 td.addText(code); 882 } else { 883 String href = context.fixReference(getCsRef(e)); 884 if (href.contains("#")) 885 href = href + "-"+Utilities.nmtokenize(code); 886 else 887 href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code); 888 if (isAbstract) 889 td.ah(href).setAttribute("title", ABSTRACT_CODE_HINT).i().addText(code); 890 else 891 td.ah(href).addText(code); 892 } 893 } 894 895 896 public String sctLink(String code) { 897// if (snomedEdition != null) 898// http://browser.ihtsdotools.org/?perspective=full&conceptId1=428041000124106&edition=us-edition&release=v20180301&server=https://prod-browser-exten.ihtsdotools.org/api/snomed&langRefset=900000000000509007 899 return "http://snomed.info/id/"+code; 900 } 901 902 private void addRefToCode(XhtmlNode td, String target, String vslink, String code) { 903 addCodeToTable(false, target, code, null, td); 904// CodeSystem cs = getContext().getWorker().fetchCodeSystem(target); 905// String cslink = getCsRef(cs); 906// String link = cslink != null ? cslink+"#"+cs.getId()+"-"+code : vslink+"#"+code; 907// if (!Utilities.isAbsoluteUrl(link)) { 908// link = getContext().getSpecificationLink()+link; 909// } 910// XhtmlNode a = td.ah(link); 911// a.addText(code); 912 } 913 914 private boolean generateComposition(XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRException, IOException { 915 boolean hasExtensions = false; 916 List<String> langs = new ArrayList<String>(); 917 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 918 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 919 scanDesignations(inc, langs, designations); 920 } 921 for (ConceptSetComponent inc : vs.getCompose().getExclude()) { 922 scanDesignations(inc, langs, designations); 923 } 924 boolean doDesignations = langs.size() + designations.size() < MAX_DESIGNATIONS_IN_LINE; 925 926 if (header) { 927 XhtmlNode h = x.h2(); 928 h.addText(vs.present()); 929 addMarkdown(x, vs.getDescription()); 930 if (vs.hasCopyrightElement()) 931 generateCopyright(x, vs); 932 } 933 int index = 0; 934 if (vs.getCompose().getInclude().size() == 1 && vs.getCompose().getExclude().size() == 0 && !VersionComparisonAnnotation.hasDeleted(vs.getCompose(), "include", "exclude")) { 935 hasExtensions = genInclude(x.ul(), vs.getCompose().getInclude().get(0), "Include", langs, doDesignations, maps, designations, index, vs) || hasExtensions; 936 } else { 937 XhtmlNode p = x.para(); 938 p.tx("This value set includes codes based on the following rules:"); 939 XhtmlNode ul = x.ul(); 940 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 941 hasExtensions = genInclude(ul, inc, "Include", langs, doDesignations, maps, designations, index, vs) || hasExtensions; 942 index++; 943 } 944 for (Base inc : VersionComparisonAnnotation.getDeleted(vs.getCompose(), "include")) { 945 genInclude(ul, (ConceptSetComponent) inc, "Include", langs, doDesignations, maps, designations, index, vs); 946 index++; 947 } 948 if (vs.getCompose().hasExclude() || VersionComparisonAnnotation.hasDeleted(vs.getCompose(), "exclude")) { 949 p = x.para(); 950 p.tx("This value set excludes codes based on the following rules:"); 951 ul = x.ul(); 952 for (ConceptSetComponent exc : vs.getCompose().getExclude()) { 953 hasExtensions = genInclude(ul, exc, "Exclude", langs, doDesignations, maps, designations, index, vs) || hasExtensions; 954 index++; 955 } 956 for (Base inc : VersionComparisonAnnotation.getDeleted(vs.getCompose(), "exclude")) { 957 genInclude(ul, (ConceptSetComponent) inc, "Exclude", langs, doDesignations, maps, designations, index, vs); 958 index++; 959 } 960 } 961 } 962 963 // now, build observed languages 964 965 if (!doDesignations && langs.size() + designations.size() > 0) { 966 Collections.sort(langs); 967 if (designations.size() == 0) { 968 x.para().b().tx("Additional Language Displays"); 969 } else if (langs.size() == 0) { 970 x.para().b().tx("Additional Designations"); 971 } else { 972 x.para().b().tx("Additional Designations and Language Displays"); 973 } 974 XhtmlNode t = x.table("codes"); 975 XhtmlNode tr = t.tr(); 976 tr.td().b().tx("Code"); 977 for (String url : designations.keySet()) { 978 tr.td().b().addText(designations.get(url)); 979 } 980 for (String lang : langs) { 981 tr.td().b().addText(describeLang(lang)); 982 } 983 for (ConceptSetComponent c : vs.getCompose().getInclude()) { 984 for (ConceptReferenceComponent cc : c.getConcept()) { 985 addDesignationRow(cc, t, langs, designations); 986 } 987 } 988 } 989 990 991 return hasExtensions; 992 } 993 994 private void renderExpansionRules(XhtmlNode x, ConceptSetComponent inc, int index, Map<String, ConceptDefinitionComponent> definitions) throws FHIRException, IOException { 995 String s = "This include specifies a heirarchy for when value sets are generated for use in a User Interface, but the rules are not properly defined"; 996 if (inc.hasExtension(ToolingExtensions.EXT_EXPAND_RULES)) { 997 String rule = inc.getExtensionString(ToolingExtensions.EXT_EXPAND_RULES); 998 if (rule != null) { 999 switch (rule) { 1000 case "all-codes": s = "This include specifies a heirarchy for when value sets are generated for use in a User Interface. The expansion contains all the codes, and also this structure:"; 1001 case "ungrouped": s = "This include specifies a heirarchy for when value sets are generated for use in a User Interface. The expansion contains this structure, and any codes not found in the structure:"; 1002 case "groups-only": s = "This include specifies a heirarchy for when value sets are generated for use in a User Interface. The expansion contains this structure:"; 1003 } 1004 } 1005 } 1006 x.br(); 1007 x.tx(s); 1008 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context.getDestDir(), context.isInlineGraphics(), true); 1009 TableModel model = gen.new TableModel("exp.h="+index, context.getRules() == GenerationRules.IG_PUBLISHER); 1010 model.setAlternating(true); 1011 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("vs.exp.header", "Code"), translate("vs.exp.hint", "The code for the item"), null, 0)); 1012 model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("vs.exp.header", "Display"), translate("vs.exp.hint", "The display for the item"), null, 0)); 1013 1014 for (Extension ext : inc.getExtensionsByUrl(ToolingExtensions.EXT_EXPAND_GROUP)) { 1015 renderExpandGroup(gen, model, ext, inc, definitions); 1016 } 1017 x.br(); 1018 x.tx("table"); 1019 XhtmlNode xn = gen.generate(model, context.getLocalPrefix(), 1, null); 1020 x.getChildNodes().add(xn); 1021 } 1022 1023 private void renderExpandGroup(HierarchicalTableGenerator gen, TableModel model, Extension ext, ConceptSetComponent inc, Map<String, ConceptDefinitionComponent> definitions) { 1024 Row row = gen.new Row(); 1025 model.getRows().add(row); 1026 row.setIcon("icon_entry_blue.png", "entry"); 1027 String code = ext.getExtensionString("code"); 1028 if (code != null) { 1029 row.getCells().add(gen.new Cell(null, null, code, null, null)); 1030 row.getCells().add(gen.new Cell(null, null, getDisplayForCode(inc, code, definitions), null, null)); 1031 } else if (ext.hasId()) { 1032 row.getCells().add(gen.new Cell(null, null, "(#"+ext.getId()+")", null, null)); 1033 row.getCells().add(gen.new Cell(null, null, ext.getExtensionString("display"), null, null)); 1034 } else { 1035 row.getCells().add(gen.new Cell(null, null, null, null, null)); 1036 row.getCells().add(gen.new Cell(null, null, ext.getExtensionString("display"), null, null)); 1037 } 1038 for (Extension member : ext.getExtensionsByUrl("member")) { 1039 Row subRow = gen.new Row(); 1040 row.getSubRows().add(subRow); 1041 subRow.setIcon("icon_entry_blue.png", "entry"); 1042 String mc = member.getValue().primitiveValue(); 1043 // mc might be a reference to another expansion group - we check that first, or to a code in the compose 1044 if (mc.startsWith("#")) { 1045 // it's a reference by id 1046 subRow.getCells().add(gen.new Cell(null, null, "("+mc+")", null, null)); 1047 subRow.getCells().add(gen.new Cell(null, null, "group reference by id", null, null)); 1048 } else { 1049 Extension tgt = findTargetByCode(inc, mc); 1050 if (tgt != null) { 1051 subRow.getCells().add(gen.new Cell(null, null, mc, null, null)); 1052 subRow.getCells().add(gen.new Cell(null, null, "group reference by code", null, null)); 1053 } else { 1054 subRow.getCells().add(gen.new Cell(null, null, mc, null, null)); 1055 subRow.getCells().add(gen.new Cell(null, null, getDisplayForCode(inc, mc, definitions), null, null)); 1056 } 1057 } 1058 } 1059 } 1060 1061 private Extension findTargetByCode(ConceptSetComponent inc, String mc) { 1062 for (Extension ext : inc.getExtensionsByUrl(ToolingExtensions.EXT_EXPAND_GROUP)) { 1063 String code = ext.getExtensionString("code"); 1064 if (mc.equals(code)) { 1065 return ext; 1066 } 1067 } 1068 return null; 1069 } 1070 1071 private String getDisplayForCode(ConceptSetComponent inc, String code, Map<String, ConceptDefinitionComponent> definitions) { 1072 for (ConceptReferenceComponent cc : inc.getConcept()) { 1073 if (code.equals(cc.getCode())) { 1074 if (cc.hasDisplay()) { 1075 return cc.getDisplay(); 1076 } 1077 } 1078 } 1079 if (definitions.containsKey(code)) { 1080 return definitions.get(code).getDisplay(); 1081 } 1082 return null; 1083 } 1084 1085 private void scanDesignations(ConceptSetComponent inc, List<String> langs, Map<String, String> designations) { 1086 for (ConceptReferenceComponent cc : inc.getConcept()) { 1087 for (Extension ext : cc.getExtension()) { 1088 if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { 1089 String lang = ToolingExtensions.readStringExtension(ext, "lang"); 1090 if (!Utilities.noString(lang) && !langs.contains(lang)) { 1091 langs.add(lang); 1092 } 1093 } 1094 } 1095 for (ConceptReferenceDesignationComponent d : cc.getDesignation()) { 1096 String lang = d.getLanguage(); 1097 if (!Utilities.noString(lang) && !langs.contains(lang)) { 1098 langs.add(lang); 1099 } else { 1100 // can we present this as a designation that we know? 1101 String disp = getDisplayForDesignation(d); 1102 String url = getUrlForDesignation(d); 1103 if (disp == null) { 1104 disp = getDisplayForUrl(url); 1105 } 1106 if (disp != null && !designations.containsKey(url)) { 1107 designations.put(url, disp); 1108 } 1109 } 1110 } 1111 } 1112 } 1113 1114 private String getDisplayForUrl(String url) { 1115 if (url == null) { 1116 return null; 1117 } 1118 switch (url) { 1119 case "http://snomed.info/sct#900000000000003001": 1120 return "Fully specified name"; 1121 case "http://snomed.info/sct#900000000000013009": 1122 return "Synonym"; 1123 default: 1124 // 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. 1125 return url; 1126 } 1127 } 1128 1129 private String getUrlForDesignation(ConceptReferenceDesignationComponent d) { 1130 if (d.hasUse() && d.getUse().hasSystem() && d.getUse().hasCode()) { 1131 return d.getUse().getSystem()+"#"+d.getUse().getCode(); 1132 } else { 1133 return null; 1134 } 1135 } 1136 1137 private String getDisplayForDesignation(ConceptReferenceDesignationComponent d) { 1138 if (d.hasUse() && d.getUse().hasDisplay()) { 1139 return d.getUse().getDisplay(); 1140 } else { 1141 return null; 1142 } 1143 } 1144 1145 private boolean genInclude(XhtmlNode ul, ConceptSetComponent inc, String type, List<String> langs, boolean doDesignations, List<UsedConceptMap> maps, Map<String, String> designations, int index, Resource vsRes) throws FHIRException, IOException { 1146 boolean hasExtensions = false; 1147 XhtmlNode li; 1148 li = ul.li(); 1149 li = renderStatus(inc, li); 1150 1151 Map<String, ConceptDefinitionComponent> definitions = new HashMap<>(); 1152 1153 if (inc.hasSystem()) { 1154 CodeSystem e = getContext().getWorker().fetchCodeSystem(inc.getSystem()); 1155 if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { 1156 li.addText(type+" all codes defined in "); 1157 addCsRef(inc, li, e); 1158 } else { 1159 if (inc.getConcept().size() > 0) { 1160 li.addText(type+" these codes as defined in "); 1161 addCsRef(inc, li, e); 1162 if (inc.hasVersion()) { 1163 li.addText(" version "); 1164 li.code(inc.getVersion()); 1165 } 1166 1167 // for performance reasons, we do all the fetching in one batch 1168 definitions = getConceptsForCodes(e, inc); 1169 1170 1171 XhtmlNode t = li.table("none"); 1172 boolean hasComments = false; 1173 boolean hasDefinition = false; 1174 for (ConceptReferenceComponent c : inc.getConcept()) { 1175 hasComments = hasComments || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT); 1176 ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode()); 1177 hasDefinition = hasDefinition || ((cc != null && cc.hasDefinition()) || ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION)); 1178 } 1179 if (hasComments || hasDefinition) 1180 hasExtensions = true; 1181 addMapHeaders(addTableHeaderRowStandard(t, false, true, hasDefinition, hasComments, false, false, null, langs, designations, doDesignations), maps); 1182 for (ConceptReferenceComponent c : inc.getConcept()) { 1183 renderConcept(inc, langs, doDesignations, maps, designations, definitions, t, hasComments, hasDefinition, c); 1184 } 1185 for (Base b : VersionComparisonAnnotation.getDeleted(inc, "concept" )) { 1186 renderConcept(inc, langs, doDesignations, maps, designations, definitions, t, hasComments, hasDefinition, (ConceptReferenceComponent) b); 1187 } 1188 } 1189 if (inc.getFilter().size() > 0) { 1190 li.addText(type+" codes from "); 1191 addCsRef(inc, li, e); 1192 li.tx(" where "); 1193 for (int i = 0; i < inc.getFilter().size(); i++) { 1194 ConceptSetFilterComponent f = inc.getFilter().get(i); 1195 if (i > 0) { 1196 if (i == inc.getFilter().size()-1) { 1197 li.tx(" and "); 1198 } else { 1199 li.tx(", "); 1200 } 1201 } 1202 XhtmlNode wli = renderStatus(f, li); 1203 if (f.getOp() == FilterOperator.EXISTS) { 1204 if (f.getValue().equals("true")) { 1205 wli.tx(f.getProperty()+" exists"); 1206 } else { 1207 wli.tx(f.getProperty()+" doesn't exist"); 1208 } 1209 } else { 1210 wli.tx(f.getProperty()+" "+describe(f.getOp())+" "); 1211 if (e != null && codeExistsInValueSet(e, f.getValue())) { 1212 String href = getContext().fixReference(getCsRef(e)); 1213 if (href.contains("#")) 1214 href = href + "-"+Utilities.nmtokenize(f.getValue()); 1215 else 1216 href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue()); 1217 wli.ah(href).addText(f.getValue()); 1218 } else if ("concept".equals(f.getProperty()) && inc.hasSystem()) { 1219 wli.addText(f.getValue()); 1220 ValidationResult vr = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions(), inc.getSystem(), inc.getVersion(), f.getValue(), null); 1221 if (vr.isOk()) { 1222 wli.tx(" ("+vr.getDisplay()+")"); 1223 } 1224 } 1225 else 1226 wli.addText(f.getValue()); 1227 String disp = ToolingExtensions.getDisplayHint(f); 1228 if (disp != null) 1229 wli.tx(" ("+disp+")"); 1230 } 1231 } 1232 } 1233 } 1234 if (inc.hasValueSet()) { 1235 li.tx(", where the codes are contained in "); 1236 boolean first = true; 1237 for (UriType vs : inc.getValueSet()) { 1238 if (first) 1239 first = false; 1240 else 1241 li.tx(", "); 1242 XhtmlNode wli = renderStatus(vs, li); 1243 AddVsRef(vs.asStringValue(), wli, vsRes); 1244 } 1245 } 1246 if (inc.hasExtension(ToolingExtensions.EXT_EXPAND_RULES) || inc.hasExtension(ToolingExtensions.EXT_EXPAND_GROUP)) { 1247 hasExtensions = true; 1248 renderExpansionRules(li, inc, index, definitions); 1249 } 1250 } else { 1251 li.tx("Import all the codes that are contained in "); 1252 if (inc.getValueSet().size() < 4) { 1253 boolean first = true; 1254 for (UriType vs : inc.getValueSet()) { 1255 if (first) 1256 first = false; 1257 else 1258 li.tx(", "); 1259 XhtmlNode wli = renderStatus(vs, li); 1260 AddVsRef(vs.asStringValue(), wli, vsRes); 1261 } 1262 } else { 1263 XhtmlNode xul = li.ul(); 1264 for (UriType vs : inc.getValueSet()) { 1265 XhtmlNode wli = renderStatus(vs, xul.li()); 1266 AddVsRef(vs.asStringValue(), wli, vsRes); 1267 } 1268 1269 } 1270 } 1271 return hasExtensions; 1272 } 1273 1274 private void renderConcept(ConceptSetComponent inc, List<String> langs, boolean doDesignations, 1275 List<UsedConceptMap> maps, Map<String, String> designations, Map<String, ConceptDefinitionComponent> definitions, 1276 XhtmlNode t, boolean hasComments, boolean hasDefinition, ConceptReferenceComponent c) { 1277 XhtmlNode tr = t.tr(); 1278 XhtmlNode td = renderStatusRow(c, t, tr); 1279 ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode()); 1280 addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td); 1281 1282 td = tr.td(); 1283 if (!Utilities.noString(c.getDisplay())) 1284 renderStatus(c.getDisplayElement(), td).addText(c.getDisplay()); 1285 else if (VersionComparisonAnnotation.hasDeleted(c, "display")) { 1286 StringType d = (StringType) VersionComparisonAnnotation.getDeletedItem(c, "display"); 1287 renderStatus(d, td).addText(d.primitiveValue()); 1288 } else if (cc != null && !Utilities.noString(cc.getDisplay())) 1289 td.style("color: #cccccc").addText(cc.getDisplay()); 1290 1291 if (hasDefinition) { 1292 td = tr.td(); 1293 if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION)) { 1294 smartAddText(td, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_DEFINITION)); 1295 } else if (cc != null && !Utilities.noString(cc.getDefinition())) { 1296 smartAddText(td, cc.getDefinition()); 1297 } 1298 } 1299 if (hasComments) { 1300 td = tr.td(); 1301 if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT)) { 1302 smartAddText(td, "Note: "+ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_VS_COMMENT)); 1303 } 1304 } 1305 if (doDesignations) { 1306 addDesignationsToRow(c, designations, tr); 1307 addLangaugesToRow(c, langs, tr); 1308 } 1309 for (UsedConceptMap m : maps) { 1310 td = tr.td(); 1311 List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap()); 1312 boolean first = true; 1313 for (TargetElementComponentWrapper mapping : mappings) { 1314 if (!first) 1315 td.br(); 1316 first = false; 1317 XhtmlNode span = td.span(null, mapping.comp.getRelationship().toString()); 1318 span.addText(getCharForRelationship(mapping.comp)); 1319 addRefToCode(td, mapping.group.getTarget(), m.getLink(), mapping.comp.getCode()); 1320 if (!Utilities.noString(mapping.comp.getComment())) 1321 td.i().tx("("+mapping.comp.getComment()+")"); 1322 } 1323 } 1324 } 1325 1326 public void addDesignationsToRow(ConceptReferenceComponent c, Map<String, String> designations, XhtmlNode tr) { 1327 for (String url : designations.keySet()) { 1328 String d = null; 1329 if (d == null) { 1330 for (ConceptReferenceDesignationComponent dd : c.getDesignation()) { 1331 if (url.equals(getUrlForDesignation(dd))) { 1332 d = dd.getValue(); 1333 } 1334 } 1335 } 1336 tr.td().addText(d == null ? "" : d); 1337 } 1338 } 1339 1340 public void addLangaugesToRow(ConceptReferenceComponent c, List<String> langs, XhtmlNode tr) { 1341 for (String lang : langs) { 1342 String d = null; 1343 for (Extension ext : c.getExtension()) { 1344 if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { 1345 String l = ToolingExtensions.readStringExtension(ext, "lang"); 1346 if (lang.equals(l)) { 1347 d = ToolingExtensions.readStringExtension(ext, "content"); 1348 } 1349 } 1350 } 1351 if (d == null) { 1352 for (ConceptReferenceDesignationComponent dd : c.getDesignation()) { 1353 String l = dd.getLanguage(); 1354 if (lang.equals(l)) { 1355 d = dd.getValue(); 1356 } 1357 } 1358 } 1359 tr.td().addText(d == null ? "" : d); 1360 } 1361 } 1362 1363 1364 private Map<String, ConceptDefinitionComponent> getConceptsForCodes(CodeSystem e, ConceptSetComponent inc) { 1365 if (e == null) { 1366 e = getContext().getWorker().fetchCodeSystem(inc.getSystem()); 1367 } 1368 1369 ValueSetExpansionComponent vse = null; 1370 if (!context.isNoSlowLookup()) { // && !getContext().getWorker().hasCache()) { removed GG 20220107 like what is this trying to do? 1371 try { 1372 ValueSetExpansionOutcome vso = getContext().getWorker().expandVS(inc, false, false); 1373 ValueSet valueset = vso.getValueset(); 1374 if (valueset == null) 1375 throw new TerminologyServiceException("Error Expanding ValueSet: "+vso.getError()); 1376 vse = valueset.getExpansion(); 1377 1378 } catch (TerminologyServiceException e1) { 1379 return null; 1380 } 1381 } 1382 1383 Map<String, ConceptDefinitionComponent> results = new HashMap<>(); 1384 List<CodingValidationRequest> serverList = new ArrayList<>(); 1385 1386 // 1st pass, anything we can resolve internally 1387 for (ConceptReferenceComponent cc : inc.getConcept()) { 1388 String code = cc.getCode(); 1389 ConceptDefinitionComponent v = null; 1390 if (e != null && code != null) { 1391 v = getConceptForCode(e.getConcept(), code); 1392 } 1393 if (v == null && vse != null) { 1394 v = getConceptForCodeFromExpansion(vse.getContains(), code); 1395 } 1396 if (v != null) { 1397 results.put(code, v); 1398 } else { 1399 serverList.add(new CodingValidationRequest(new Coding(inc.getSystem(), code, null))); 1400 } 1401 } 1402 if (!context.isNoSlowLookup() && !serverList.isEmpty()) { 1403 getContext().getWorker().validateCodeBatch(getContext().getTerminologyServiceOptions(), serverList, null); 1404 for (CodingValidationRequest vr : serverList) { 1405 ConceptDefinitionComponent v = vr.getResult().asConceptDefinition(); 1406 if (v != null) { 1407 results.put(vr.getCoding().getCode(), v); 1408 } 1409 } 1410 } 1411 return results; 1412 } 1413 1414 private ConceptDefinitionComponent getConceptForCode(List<ConceptDefinitionComponent> list, String code) { 1415 for (ConceptDefinitionComponent c : list) { 1416 if (code.equals(c.getCode())) 1417 return c; 1418 ConceptDefinitionComponent v = getConceptForCode(c.getConcept(), code); 1419 if (v != null) 1420 return v; 1421 } 1422 return null; 1423 } 1424 1425 private ConceptDefinitionComponent getConceptForCodeFromExpansion(List<ValueSetExpansionContainsComponent> list, String code) { 1426 for (ValueSetExpansionContainsComponent c : list) { 1427 if (code.equals(c.getCode())) { 1428 ConceptDefinitionComponent res = new ConceptDefinitionComponent(); 1429 res.setCode(c.getCode()); 1430 res.setDisplay(c.getDisplay()); 1431 return res; 1432 } 1433 ConceptDefinitionComponent v = getConceptForCodeFromExpansion(c.getContains(), code); 1434 if (v != null) 1435 return v; 1436 } 1437 return null; 1438 } 1439 1440 1441 private boolean codeExistsInValueSet(CodeSystem cs, String code) { 1442 for (ConceptDefinitionComponent c : cs.getConcept()) { 1443 if (inConcept(code, c)) 1444 return true; 1445 } 1446 return false; 1447 } 1448 1449 1450 1451 private void addDesignationRow(ConceptReferenceComponent c, XhtmlNode t, List<String> langs, Map<String, String> designations) { 1452 XhtmlNode tr = t.tr(); 1453 tr.td().addText(c.getCode()); 1454 addDesignationsToRow(c, designations, tr); 1455 addLangaugesToRow(c, langs, tr); 1456 } 1457 1458 1459 private String describe(FilterOperator op) { 1460 if (op == null) 1461 return " null "; 1462 switch (op) { 1463 case EQUAL: return " = "; 1464 case ISA: return " is-a "; 1465 case ISNOTA: return " is-not-a "; 1466 case REGEX: return " matches (by regex) "; 1467 case NULL: return " ?ngen-13? "; 1468 case IN: return " in "; 1469 case NOTIN: return " not in "; 1470 case DESCENDENTOF: return " descends from "; 1471 case EXISTS: return " exists "; 1472 case GENERALIZES: return " generalizes "; 1473 } 1474 return null; 1475 } 1476 1477 1478 1479 1480 1481 private boolean inConcept(String code, ConceptDefinitionComponent c) { 1482 if (c.hasCodeElement() && c.getCode().equals(code)) 1483 return true; 1484 for (ConceptDefinitionComponent g : c.getConcept()) { 1485 if (inConcept(code, g)) 1486 return true; 1487 } 1488 return false; 1489 } 1490 1491 1492}