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