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