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