
001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.HashMap; 007import java.util.HashSet; 008import java.util.List; 009import java.util.Map; 010import java.util.Set; 011 012import lombok.extern.slf4j.Slf4j; 013import org.hl7.fhir.exceptions.DefinitionException; 014import org.hl7.fhir.exceptions.FHIRException; 015import org.hl7.fhir.exceptions.FHIRFormatError; 016import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions; 017import org.hl7.fhir.r5.context.ContextUtilities; 018import org.hl7.fhir.r5.model.ElementDefinition; 019import org.hl7.fhir.r5.model.Resource; 020import org.hl7.fhir.r5.model.StructureDefinition; 021import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 022import org.hl7.fhir.r5.renderers.utils.RenderingContext; 023import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 024import org.hl7.fhir.r5.renderers.utils.ResourceWrapper.NamedResourceWrapperList; 025import org.hl7.fhir.r5.utils.EOperationOutcome; 026import org.hl7.fhir.r5.utils.ToolingExtensions; 027import org.hl7.fhir.r5.utils.XVerExtensionManager; 028import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus; 029import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 030import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 031import org.hl7.fhir.utilities.Utilities; 032import org.hl7.fhir.utilities.xhtml.NodeType; 033import org.hl7.fhir.utilities.xhtml.XhtmlNode; 034 035@MarkedToMoveToAdjunctPackage 036@Slf4j 037public class ProfileDrivenRenderer extends ResourceRenderer { 038 039 private Set<String> containedIds = new HashSet<>(); 040 041 public ProfileDrivenRenderer(RenderingContext context) { 042 super(context); 043 } 044 045 @Override 046 public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException { 047 renderResourceTechDetails(r, x); 048 try { 049 StructureDefinition sd = context.getContext().fetchTypeDefinition(r.fhirType()); 050 if (sd == null) { 051 throw new FHIRException(context.formatPhrase(RenderingContext.PROF_DRIV_FEXCP, r.fhirType())+" "); 052 } else { 053 ElementDefinition ed = sd.getSnapshot().getElement().get(0); 054 containedIds.clear(); 055 generateByProfile(status, r, sd, r, ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), context.isTechnicalMode(), 0); 056 } 057 } catch (Exception e) { 058 log.debug(context.formatPhrase(RenderingContext.PROF_DRIV_ERR_GEN_NARR) +r.fhirType()+"/"+r.getId()+": "+e.getMessage(), e); 059 x.para().b().style("color: maroon").tx(context.formatPhrase(RenderingContext.PROF_DRIV_EXCP, e.getMessage())+" "); 060 } 061 } 062 063 064 @Override 065 public String buildSummary(ResourceWrapper res) throws UnsupportedEncodingException, IOException { 066 StructureDefinition profile = getContext().getWorker().fetchTypeDefinition(res.fhirType()); 067 if (profile == null) 068 return "unknown resource type " +res.fhirType(); 069 else { 070 List<ResourceWrapper> children = res.children(); 071 ContextUtilities cu = res.getContextUtilities(); 072 for (ResourceWrapper p : children) { 073 if (p.name().equals("title") && cu.isDatatype(p.fhirType()) && !p.isEmpty()) { 074 return res.fhirType()+" "+ displayDataType(p); 075 } 076 } 077 for (ResourceWrapper p : children) { 078 if (p.name().equals("name") && cu.isDatatype(p.fhirType()) && !p.isEmpty()) { 079 return res.fhirType()+" "+ displayDataType(p); 080 } 081 } 082 for (ResourceWrapper p : children) { 083 if (p.name().equals("code") && cu.isDatatype(p.fhirType()) && !p.isEmpty()) { 084 return res.fhirType()+" "+ displayDataType(p); 085 } 086 } 087 switch (res.fhirType()) { 088 case "Binary" : return res.fhirType()+": "+res.primitiveValue("contentType")+" ("+res.primitiveValue("data").length()+" bytes base64)"; 089 } 090 return generateResourceSummary(res, profile, profile.getSnapshot().getElementFirstRep(), false, false); 091 } 092 } 093 094 public String generateResourceSummary(ResourceWrapper res, StructureDefinition sd, ElementDefinition ed, boolean showCodeDetails, boolean canLink) throws FHIRException, UnsupportedEncodingException, IOException { 095 if (sd == null) 096 return "unknown resource " +res.fhirType(); 097 else { 098 SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, ed, true); 099 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("; "); 100 for (NamedResourceWrapperList p : res.childrenInGroups()) { 101 ElementDefinition pDefn = getElementDefinition(childDefs, p); 102 if (pDefn != null && !ignoreProperty(p) && !pDefn.getBase().getPath().startsWith("Resource.")) { 103 if (p.getValues().size() > 0 && p.getValues().get(0) != null && pDefn != null && isSimple(pDefn) && includeInSummary(pDefn, p)) { 104 CommaSeparatedStringBuilder b2 = new CommaSeparatedStringBuilder(","); 105 for (ResourceWrapper v : p.getValues()) { 106 b2.append(displayDataType(v)); 107 } 108 b.append(formatPhrase(RenderingContext.PROF_DRIV_SUMM_PROP, labelForElement(pDefn), b2.toString())); 109 } 110 } 111 } 112 if (b.length() == 0) { 113 return formatPhrase(RenderingContext.PROF_DRIV_SUMM_NONE, res.fhirType()); 114 } else { 115 return formatPhrase(RenderingContext.PROF_DRIV_SUMM, res.fhirType(), b.toString()); 116 } 117 } 118 } 119 120 private String labelForElement(ElementDefinition pDefn) { 121 return pDefn.getName(); 122 } 123 124 private ElementDefinition getElementDefinition(SourcedChildDefinitions childDefs, NamedResourceWrapperList p) { 125 for (ElementDefinition ed : childDefs.getList()) { 126 if (ed.getName().equals(p.getName())) { 127 return ed; 128 } 129 } 130 return null; 131 } 132 133 private boolean ignoreProperty(NamedResourceWrapperList p) { 134 return Utilities.existsInList(p.getName(), "contained"); 135 } 136 137 private boolean includeInSummary(ElementDefinition child, NamedResourceWrapperList list) throws UnsupportedEncodingException, FHIRException, IOException { 138 if (child.getName().endsWith("active") && list != null && list.getValues().size() > 0 && "true".equals(list.getValues().get(0).primitiveValue())) { 139 return false; 140 } 141 if (child.getIsModifier()) 142 return true; 143 if (child.getMustSupport()) 144 return true; 145 if (child.getType().size() == 1) { 146 String t = child.getType().get(0).getWorkingCode(); 147 if (t.equals("Address") || t.equals("Contact") || t.equals("Reference") || t.equals("Uri") || t.equals("Url") || t.equals("Canonical")) 148 return false; 149 } 150 return true; 151 } 152 153 private ElementDefinition getElementDefinition(List<ElementDefinition> elements, String path) { 154 for (ElementDefinition element : elements) 155 if (element.getPath().equals(path)) 156 return element; 157 return null; 158 } 159 160 private void renderLeaf(RenderingStatus status, ResourceWrapper res, ResourceWrapper ew, StructureDefinition sd, ElementDefinition defn, XhtmlNode parent, XhtmlNode x, boolean title, boolean showCodeDetails, Map<String, String> displayHints, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 161 if (ew == null) 162 return; 163 164 if (context.isShowComments()) { 165 x = renderCommentsSpan(x, ew); 166 } 167 if (Utilities.existsInList(ew.fhirType(), "Extension") || ew.isResource()) { 168 return; 169 } else if (ew.fhirType().equals("ElementDefinition")) { 170 x.tx("todo-bundle"); 171 } else if (!renderDataType(status, parent, x, ew)) { 172 // well, we have a cell (x) to render this thing, whatever it is 173 // it's not a data type for which we have a built rendering, so we're going to get a list of it's renderable datatype properties, and render them in a list 174 // SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, defn); 175 boolean first = true; 176 x.tx(" ("); 177 for (ResourceWrapper child : ew.children()) { 178// ElementDefinition childDefn = getElementDefinition(childDefs.getList(), child.name()); 179 if (child != null && !"Extension".equals(child.fhirType()) && canRenderDataType(child.fhirType())) { 180 if (first) { 181 first = false; 182 } else { 183 x.tx("; "); 184 } 185 x.tx(context.formatMessage(RenderingContext.GENERAL_DATA_DISPLAY_PROPERTY, child.name(), displayDataType(child))); 186 } 187 } 188 x.tx(")"); 189 } 190 } 191 192 private XhtmlNode renderCommentsSpan(XhtmlNode x, ResourceWrapper e) { 193 if (e.hasFormatComment()) { 194 return x.span(null, CommaSeparatedStringBuilder.join(" ", e.getFormatCommentsPre())); 195 } else { 196 return x; 197 } 198 } 199 200// private boolean displayLeaf(ResourceWrapper res, ResourceWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails, boolean allowLinks) throws FHIRException, UnsupportedEncodingException, IOException { 201// if (ew == null) 202// return false; 203// 204// Map<String, String> displayHints = readDisplayHints(defn); 205// 206// if (name.endsWith("[x]")) 207// name = name.substring(0, name.length() - 3); 208// 209// if (!showCodeDetails && ew.isPrimitive() && isDefault(displayHints, ew)) { 210// return false; 211// } else if (Utilities.existsInList(ew.fhirType(), "Extension")) { 212// return false; 213// } else { 214// x.addText(name+": "+ displayDataType(ew)); 215// return true; 216// } 217// } 218 219 220 221 private boolean isSimple(ElementDefinition e) { 222 //we can tell if e is a primitive because it has types 223 if (e.getType().isEmpty()) { 224 return false; 225 } 226 if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) { 227 return false; 228 } 229 if (e.getType().size() > 1) { 230 return true; 231 } 232 StructureDefinition sd = context.getWorker().fetchTypeDefinition(e.getTypeFirstRep().getCode()); 233 if (sd != null) { 234 if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 235 return true; 236 } 237 if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) { 238 if (Utilities.existsInList(e.getTypeFirstRep().getCode(), "Extension", "CodeableConcept", "Coding", "Annotation", "Identifier", "HumanName", "SampledData", 239 "Address", "ContactPoint", "ContactDetail", "Timing", "Range", "Quantity", "Ratio", "Period", "Reference")) { 240 return true; 241 } 242 } 243 } 244 return false; 245 } 246 247 private boolean isBase(String code) { 248 return code != null && (code.equals("Element") || code.equals("BackboneElement")); 249 } 250 251 private SourcedChildDefinitions getChildrenForPath(StructureDefinition profile, String path) throws DefinitionException { 252 var elements = profile.getSnapshot().getElement(); 253 // do we need to do a name reference substitution? 254 for (ElementDefinition e : elements) { 255 if (e.getPath().equals(path) && e.hasContentReference()) { 256 String ref = e.getContentReference(); 257 ElementDefinition t = null; 258 // now, resolve the name 259 for (ElementDefinition e1 : elements) { 260 if (ref.equals("#"+e1.getId())) 261 t = e1; 262 } 263 if (t == null) 264 throw new DefinitionException("Unable to resolve content reference "+ref+" trying to resolve "+path); 265 path = t.getPath(); 266 break; 267 } 268 } 269 270 ElementDefinition t = null; 271 List<ElementDefinition> results = new ArrayList<ElementDefinition>(); 272 for (ElementDefinition e : elements) { 273 if (e.getPath().equals(path)) { 274 t = e; 275 } 276 if (e.getPath().startsWith(path+".") && !e.getPath().substring(path.length()+1).contains(".")) 277 results.add(e); 278 } 279 if (results.isEmpty() && t != null && t.getType().size() == 1) { 280 StructureDefinition tsd = context.getWorker().fetchTypeDefinition(t.getTypeFirstRep().getWorkingCode()); 281 return getChildrenForPath(tsd, tsd.getType()); 282 } 283 return new SourcedChildDefinitions(profile, results, path); 284 } 285 286 private void generateByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, ResourceWrapper e, ElementDefinition defn, List<ElementDefinition> children, XhtmlNode x, String path, boolean showCodeDetails, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 287 if (children.isEmpty()) { 288 StructureDefinition sdt = context.getWorker().fetchTypeDefinition(e.fhirType()); 289 if (sdt != null && (sdt.getKind() == StructureDefinitionKind.COMPLEXTYPE || sdt.getKind() == StructureDefinitionKind.PRIMITIVETYPE)) { 290 renderLeaf(status, res, e, profile, defn, x, x, false, showCodeDetails, readDisplayHints(defn), indent); 291 } else { 292 // we don't have anything to render? 293 } 294 } else { 295 List<NamedResourceWrapperList> pl = splitExtensions(profile, e.childrenInGroups()); 296 for (NamedResourceWrapperList p : pl) { 297 generateForProperty(status, res, profile, children, x, path, showCodeDetails, indent, false, p); 298 } 299 for (NamedResourceWrapperList p : pl) { 300 generateForProperty(status, res, profile, children, x, path, showCodeDetails, indent, true, p); 301 } 302 } 303 } 304 305 private void generateForProperty(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, 306 List<ElementDefinition> children, XhtmlNode x, String path, 307 boolean showCodeDetails, int indent, boolean round2, NamedResourceWrapperList p) 308 throws UnsupportedEncodingException, IOException, EOperationOutcome { 309 if (!p.getValues().isEmpty()) { 310 ElementDefinition child = getElementDefinition(children, path+"."+p.getName()); 311 if (child != null) { 312 if (!child.getBase().hasPath() || !child.getBase().getPath().startsWith("Resource.")) { 313 generateElementByProfile(status, res, profile, x, path, showCodeDetails, indent, p, child, round2); 314 } 315 } 316 } 317 } 318 319 public void generateElementByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, XhtmlNode x, String path, 320 boolean showCodeDetails, int indent, NamedResourceWrapperList p, ElementDefinition child, boolean round2) throws UnsupportedEncodingException, IOException, EOperationOutcome { 321 Map<String, String> displayHints = readDisplayHints(child); 322 if ("DomainResource.contained".equals(child.getBase().getPath())) { 323 if (round2) { 324 for (ResourceWrapper v : p.getValues()) { 325 RenderingContext ctxt = context.forContained(); 326 if (v.getResourceWrapper() != null && !RendererFactory.hasSpecificRenderer(v.fhirType())) { 327 x.hr(); 328 ResourceRenderer rnd = RendererFactory.factory(v.fhirType(), ctxt); 329 rnd.buildNarrative(status, x.blockquote(), v); 330 } 331 } 332 } 333 } else if (!round2 && !exemptFromRendering(child)) { 334 boolean isExt = isExtension(p); 335 if (isExt) { 336 status.setExtensions(true); 337 } 338 SourcedChildDefinitions grandChildren = getChildrenForPath(profile, path+"."+p.getName()); 339 filterGrandChildren(grandChildren.getList(), path+"."+p.getName(), p); 340 if (p.getValues().size() > 0) { 341 if (isSimple(child) && !isExt) { 342 XhtmlNode para = x.isPara() ? para = x : x.para(); 343 String name = p.getName(); 344 if (name.endsWith("[x]")) 345 name = name.substring(0, name.length() - 3); 346 if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) { 347 348 para.b().addText(name); 349 para.tx(": "); 350 if (renderAsList(child) && p.getValues().size() > 1) { 351 XhtmlNode list = x.ul(); 352 for (ResourceWrapper v : p.getValues()) 353 renderLeaf(status, res, v, profile, child, x, list.li(), false, showCodeDetails, displayHints, indent); 354 } else { 355 boolean first = true; 356 for (ResourceWrapper v : p.getValues()) { 357 if (first) { 358 first = false; 359 } else { 360 para.tx(", "); 361 } 362 renderLeaf(status, res, v, profile, child, x, para, false, showCodeDetails, displayHints, indent); 363 } 364 } 365 } 366 } else if (canDoTable(path, p, grandChildren.getList(), x)) { 367 XhtmlNode xn = new XhtmlNode(NodeType.Element, getHeader()); 368 xn.addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); 369 XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); 370 tbl.setAttribute("class", "grid"); 371 XhtmlNode tr = tbl.tr(); 372 tr.td().style("display: none").tx("-"); // work around problem with empty table rows 373 boolean add = addColumnHeadings(tr, grandChildren.getList()); 374 for (ResourceWrapper v : p.getValues()) { 375 if (v != null) { 376 tr = tbl.tr(); 377 tr.td().style("display: none").tx("*"); // work around problem with empty table rows 378 add = addColumnValues(status, res, tr, profile, grandChildren.getList(), v, showCodeDetails, displayHints, indent) || add; 379 } 380 } 381 if (add) { 382 x.add(xn); 383 x.add(tbl); 384 } 385 } else if (isExtension(p)) { 386 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, p.getUrl()); 387 for (ResourceWrapper v : p.getValues()) { 388 if (v != null) { 389 ResourceWrapper vp = v.child("value"); 390 List<ResourceWrapper> ev = v.children("extension"); 391 if (vp != null) { 392 XhtmlNode para = x.para(); 393 para.b().addText(labelforExtension(sd, p.getUrl())); 394 para.tx(": "); 395 renderLeaf(status, res, vp, profile, child, x, para, false, showCodeDetails, displayHints, indent); 396 } else if (!ev.isEmpty()) { 397 XhtmlNode bq = x.addTag("blockquote"); 398 bq.para().b().addText(labelforExtension(sd, p.getUrl())); 399 // what happens now depends. If all the children are simple extensions, they'll be rendered as properties 400 boolean allSimple = true; 401 for (ResourceWrapper vv : ev) { 402 if (!vv.has("value")) { 403 allSimple = false; 404 } 405 } 406 if (allSimple) { 407 XhtmlNode ul = bq.ul(); 408 for (ResourceWrapper vv : ev) { 409 XhtmlNode li = ul.li(); 410 li.tx(labelForSubExtension(vv.primitiveValue("url"), sd)); 411 li.tx(": "); 412 renderLeaf(status, res, vv.child("value"), sd, child, x, li, isExt, showCodeDetails, displayHints, indent); 413 } 414 } else { 415 for (ResourceWrapper vv : ev) { 416 StructureDefinition ex = context.getWorker().fetchTypeDefinition("Extension"); 417 SourcedChildDefinitions children = getChildrenForPath(ex, "Extension"); 418 generateByProfile(status, res, ex, vv, child, children.getList(), bq, "Extension", showCodeDetails, indent+1); 419 } 420 } 421 } 422 } 423 } 424 } else { 425 for (ResourceWrapper v : p.getValues()) { 426 if (v != null) { 427 XhtmlNode bq = x.addTag("blockquote"); 428 bq.para().b().addText(p.getName()); 429 generateByProfile(status, res, grandChildren.getSource(), v, child, grandChildren.getList(), bq, grandChildren.getPath(), showCodeDetails, indent+1); 430 } 431 } 432 } 433 } 434 } 435 } 436 437// 438// private String getGrandChildBase(List<ElementDefinition> grandChildren) { 439// if (grandChildren == null || grandChildren.isEmpty()) { 440// return null; 441// } 442// String[] path = grandChildren.get(0).getPath().split("\\."); 443// for (int i = 1; i < grandChildren.size(); i++) { 444// path = getSharedString(path, grandChildren.get(1).getPath().split("\\.")); 445// } 446// return CommaSeparatedStringBuilder.join(".", path); 447// } 448// 449// private String[] getSharedString(String[] path, String[] path2) { 450// int m = -1; 451// for (int i = 0; i < Integer.min(path.length, path2.length); i++) { 452// if (path[i].equals(path2[i])) { 453// m = i; 454// } else { 455// break; 456// } 457// } 458// return m == -1 ? new String[0] : Arrays.copyOfRange(path, 0, m+1); 459// } 460 461 private String labelForSubExtension(String url, StructureDefinition sd) { 462 return url; 463 } 464 465 private String labelforExtension(StructureDefinition sd, String url) { 466 if (sd == null) { 467 return tail(url); 468 } else { 469 return sd.present(); 470 } 471 } 472 473 private String getHeader() { 474 int i = 3; 475 while (i <= context.getHeaderLevelContext()) 476 i++; 477 if (i > 6) 478 i = 6; 479 return "h"+Integer.toString(i); 480 } 481 482 private List<ResourceWrapper> getValues(String path, NamedResourceWrapperList p, ElementDefinition e) { 483 List<ResourceWrapper> res = new ArrayList<ResourceWrapper>(); 484 for (ResourceWrapper v : p.getValues()) { 485 for (ResourceWrapper g : v.children()) { 486 if ((path+"."+p.getName()+"."+g.name()).equals(e.getPath())) 487 res.add(v); 488 } 489 } 490 return res; 491 } 492 493 private boolean canDoTable(String path, NamedResourceWrapperList p, List<ElementDefinition> grandChildren, XhtmlNode x) { 494 if (isExtension(p)) { 495 return false; 496 } 497 if (x.getName().equals("p")) { 498 return false; 499 } 500 501 if (grandChildren.size() == 0) { 502 return false; 503 } 504 505 for (ElementDefinition e : grandChildren) { 506 List<ResourceWrapper> values = getValues(path, p, e); 507 if (values.size() > 1 || !isSimple(e) || !canCollapse(e)) 508 return false; 509 } 510 return true; 511 } 512 513 public boolean isExtension(NamedResourceWrapperList p) { 514 return p.getUrl() != null; 515 } 516 517 518 private boolean canCollapse(ElementDefinition e) { 519 // we can collapse any data type 520 return !e.getType().isEmpty(); 521 } 522 private boolean exemptFromRendering(ElementDefinition child) { 523 if (child == null) 524 return false; 525 if ("DomainResource.text".equals(child.getBase().getPath())) { 526 return true; 527 } 528 if ("Composition.subject".equals(child.getPath())) { 529 return true; 530 } 531 if ("Composition.section".equals(child.getPath())) { 532 return true; 533 } 534 return false; 535 } 536 537 private boolean renderAsList(ElementDefinition child) { 538 if (child.getType().size() == 1) { 539 String t = child.getType().get(0).getWorkingCode(); 540 if (t.equals("Address") || t.equals("Reference")) 541 return true; 542 } 543 return false; 544 } 545 546 private boolean addColumnHeadings(XhtmlNode tr, List<ElementDefinition> grandChildren) { 547 boolean b = false; 548 for (ElementDefinition e : grandChildren) { 549 b = true; 550 tr.td().b().addText(Utilities.capitalize(tail(e.getPath()))); 551 } 552 return b; 553 } 554 555 private boolean addColumnValues(RenderingStatus status, ResourceWrapper res, XhtmlNode tr, StructureDefinition profile, List<ElementDefinition> grandChildren, ResourceWrapper v, boolean showCodeDetails, Map<String, String> displayHints, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { 556 boolean b = false; 557 for (ElementDefinition e : grandChildren) { 558 List<ResourceWrapper> p = v.children(e.getPath().substring(e.getPath().lastIndexOf(".")+1)); 559 XhtmlNode td = tr.td(); 560 if (p == null || p.size() == 0) { 561 b = true; 562 td.tx(" "); 563 } else { 564 for (ResourceWrapper vv : p) { 565 b = true; 566 td.sep(", "); 567 renderLeaf(status, res, vv, profile, e, td, td, false, showCodeDetails, displayHints, indent); 568 } 569 } 570 } 571 return b; 572 } 573 574 private void filterGrandChildren(List<ElementDefinition> grandChildren, String string, NamedResourceWrapperList prop) { 575 List<ElementDefinition> toRemove = new ArrayList<ElementDefinition>(); 576 toRemove.addAll(grandChildren); 577 for (ResourceWrapper b : prop.getValues()) { 578 List<ElementDefinition> list = new ArrayList<ElementDefinition>(); 579 for (ElementDefinition ed : toRemove) { 580 List<ResourceWrapper> p = b.children(tail(ed.getPath())); 581 if (p != null && !p.isEmpty()) 582 list.add(ed); 583 } 584 toRemove.removeAll(list); 585 } 586 grandChildren.removeAll(toRemove); 587 } 588 589 private List<NamedResourceWrapperList> splitExtensions(StructureDefinition profile, List<NamedResourceWrapperList> children) throws UnsupportedEncodingException, IOException, FHIRException { 590 List<NamedResourceWrapperList> results = new ArrayList<NamedResourceWrapperList>(); 591 for (NamedResourceWrapperList p : children) { 592 if (p.getName().equals("extension") || p.getName().equals("modifierExtension")) { 593 // we're going to split these up, and create a property for each url 594 for (ResourceWrapper v : p.getValues()) { 595 String url = v.primitiveValue("url"); 596 if (url != null) { 597 // 1. check extension is valid 598 StructureDefinition ed = getContext().getWorker().fetchResource(StructureDefinition.class, url); 599 if (ed == null) { 600 if (xverManager == null) { 601 xverManager = new XVerExtensionManager(context.getWorker()); 602 } 603 if (xverManager.matchingUrl(url) && xverManager.status(url) == XVerExtensionStatus.Valid) { 604 ed = xverManager.makeDefinition(url); 605 new ContextUtilities(getContext().getWorker()).generateSnapshot(ed); 606 getContext().getWorker().cacheResource(ed); 607 } 608 } 609 if (p.getName().equals("modifierExtension") && ed == null) { 610 throw new DefinitionException("Unknown modifier extension "+url); 611 } else { 612 // nothing 613 } 614 615 // 2. Park it 616 NamedResourceWrapperList nl = null; 617 for (NamedResourceWrapperList t : results) { 618 if (t.getUrl() != null && t.getUrl().equals(url)) { 619 nl = t; 620 } 621 } 622 if (nl == null) { 623 nl = new NamedResourceWrapperList(p.getName(), url); 624 results.add(nl); 625 } 626 nl.getValues().add(v); 627 } 628 } 629 } else { 630 results.add(p); 631 } 632 } 633 return results; 634 } 635 636 637 private Map<String, String> readDisplayHints(ElementDefinition defn) throws DefinitionException { 638 Map<String, String> hints = new HashMap<String, String>(); 639 if (defn != null) { 640 String displayHint = ToolingExtensions.getDisplayHint(defn); 641 if (!Utilities.noString(displayHint)) { 642 String[] list = displayHint.split(";"); 643 for (String item : list) { 644 String[] parts = item.split(":"); 645 if (parts.length == 1) { 646 hints.put("value", parts[0].trim()); 647 } else { 648 if (parts.length != 2) { 649 throw new DefinitionException("error reading display hint: '"+displayHint+"'"); 650 } 651 hints.put(parts[0].trim(), parts[1].trim()); 652 } 653 } 654 } 655 } 656 return hints; 657 } 658 659 @SuppressWarnings("rawtypes") 660 private boolean isDefaultValue(Map<String, String> displayHints, List<ResourceWrapper> list) throws UnsupportedEncodingException, IOException, FHIRException { 661 if (list.size() != 1) 662 return false; 663 if (list.get(0).isPrimitive()) 664 return isDefault(displayHints, list.get(0)); 665 else 666 return false; 667 } 668 669 private boolean isDefault(Map<String, String> displayHints, ResourceWrapper primitiveType) { 670 String v = primitiveType.primitiveValue(); 671 if (!Utilities.noString(v) && displayHints.containsKey("default") && v.equals(displayHints.get("default"))) 672 return true; 673 return false; 674 } 675 676 677 protected String tail(String path) { 678 return path.substring(path.lastIndexOf(".")+1); 679 } 680 681 protected String utail(String path) { 682 return path.contains("/") ? path.substring(path.lastIndexOf("/")+1) : path; 683 } 684 685 public boolean canRender(Resource resource) { 686 return context.getWorker().getResourceNames().contains(resource.fhirType()); 687 } 688 689 public RendererType getRendererType() { 690 return RendererType.PROFILE; 691 } 692 693}