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