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