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